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.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.Map;
030import java.util.Set;
031
032import org.biojava.bio.symbol.Location;
033import org.biojava.utils.ChangeVetoException;
034import org.biojava.utils.SmallMap;
035
036/**
037 * A set of constraints on the data contained in an <code>Annotation</code>.
038 * <code>AnnotationType</code> instances can be used to validate an
039 * <code>Annotation</code> to check that it has the appropriate
040 * properties and that they are of the right type.
041 *
042 * <h2>Common Usage</h2>
043 *
044 * <pre>
045 * // annType is going to define ID as being a single String and
046 * // AC as being a list of Strings and accept any other properties at all
047 * AnnotationType annType = new AnnotationType.Impl();
048 * annType.setDefaultConstraints(PropertyConstraint.ANY, Cardinality.ANY)
049 * annType.setConstraints("ID",
050 *                        new PropertyConstraint.ByClass(String.class),
051 *                        CardinalityConstraint.ONE );
052 * annType.setConstraint("AC",
053 *                       new PropertyConstraint.ByClass(String.class),
054 *                       CardinalityConstraint.ANY );
055 *
056 * Annotation ann = ...;
057 *
058 * if(annType.accepts(ann)) {
059 *   // we have proved that ann contains one ID property that is a String
060 *   System.out.println("ID: " + (String) ann.getProperty("ID"));
061 *   // we know that the AC property is potentialy more than one String -
062 *   // let's use getProperty on AnnotationType to make sure that the
063 *   // values get unpacked cleanly
064 *   System.out.println("AC:");
065 *   for(Iterator i = annType.getProperty(ann, "AC"); i.hasNext(); ) {
066 *     System.out.println("\t" + (String) i.next());
067 *   }
068 * } else {
069 *   throw new IllegalArgumentException(
070 *     "Expecting an annotation conforming to: "
071 *     annType + " but got: " + ann
072 *   );
073 * }
074 * </pre>
075 *
076 * <h2>Description</h2>
077 *
078 * <p>AnnotationType is a  constraint-based language for describing
079 * sets of Annotation bundles. It works by assuming that any given Annotation
080 * may have any set of properties defined. If it matches a particular
081 * AnnotationType instance, then each defined property must be of a value
082 * that is acceptable to the type, and each undefined property must
083 * be allowed to be absend in the type.</p>
084 *
085 * <p>
086 * <code>AnnotationType</code> imposes a data model on <code>Annotations</code>
087 * where the value of each `slot' is considered as a <code>Collection</code>.
088 * Values which aren't actually <code>Collection</code> instances are treated
089 * as singleton sets.  Undefined properties are treated as <code>Collection.EMPTY_SET</code>.
090 * The logic to validate a complete slot in an <code>Annotation</code> is
091 * encapsulated by a <code>CollectionConstraint</code> object.  In the common case,
092 * slots are validated by <code>CollectionConstraint.AllValuesIn</code> which
093 * ensures that all members of the collection match a specified <code>PropertyConstraint</code>,
094 * and that the size of the collection matches a cardinality constraint.  This is the
095 * most important type of constraint when defining datatypes.  However, when using
096 * <code>AnnotationTypes</code> to specify a query over a set of Annotations, it may be more
097 * useful to ask whether a slot <em>contains</em> a given value:  For example
098 * </p>
099 *
100 * <pre>
101 * // Test that gene_name contains the value "BRCA2", ignoring other values (synonyms)
102 * // which might be present in that slot.
103 * AnnotationType at = new AnnotationType.Impl();
104 * at.setConstraint(
105 *      "gene_name",
106 *      new CollectionConstraint.Contains(
107 *          new PropertyConstraint.ExactValue("BRCA2"),
108 *          CardinalityConstraint.ONE
109 *      )
110 * );
111 * </pre>
112 *
113 * <p>It is usually left up to the AnnotationType instance to work out how
114 * multiple values should be packed into a single property slot in an Annotation
115 * instance. Commonly, things that are allowed a cardinality of 1 will store one
116 * value directly in the slot. Things that allow multiple values (and optionaly
117 * things with one value) will usualy store them within a Collection in the
118 * slot. This complexity is hidden from you if you use the accessor methods
119 * built into AnnotationType, setProperty(), addProperty(), removeProperty() and getProperty().
120 * </p>
121 *
122
123 *
124 * 
125 * Using AnnotationType instances that you have been provided with
126 * e.g. from UnigeneTools.LIBRARY_ANNOTATION
127 *
128 * 
129 * <code>AnnotationType</code> instances can be used as queries
130 * to select from a set of Annotations based on the value of one
131 * or more properties.  Commonly, this is used in conjunction
132 * with <code>FeatureFilter.ByAnnotationType</code>.
133 *
134 * 
135 * Make AnnotationType instances that describe what should and
136 * should not appear in an Annotation bundle
137 *
138 * 
139 * Constrain FeatureFilter schemas by Annotation associated with
140 * the features
141 *
142 * 
143 * Provide meta-data to the tag-value parser for automatically
144 * generating object representations of flat-files
145 *
146 * 
147 * Implementing your own AnnotationType implementations to reflect
148 * frame, schema or ontology definitions. For example, dynamically
149 * reflect an RDMBS schema or DAML/Oil deffinition as an
150 * AnnotationType.
151 * 
152 * @since 1.3
153 * @author Matthew Pocock
154 * @author Keith James (docs)
155 * @author Thomas Down
156 * 
157 */
158public interface AnnotationType {
159    /**
160     * The type that accepts all annotations and is the supertype of all
161     * other annotations. Only an empty annotation is an exact instance of
162     * this type.
163     *
164     *  Use this whenever an AnnotationType is needed by an API and you
165     *       don't want to constrain anything
166     */
167    AnnotationType ANY = new Impl(
168      PropertyConstraint.ANY,
169      CardinalityConstraint.ANY
170    );
171
172    /**
173     * The type that accepts no annotations at all and is the subtype of all
174     * other annotations.
175     *
176     * Use this whenever an AnnotationType is needed by an API and you
177     *       want to make sure that all Annotation objects get rejected
178     */
179    AnnotationType NONE = new Impl(
180      PropertyConstraint.NONE,
181      CardinalityConstraint.NONE
182    );
183
184    /**
185     * Validate an Annotation against this AnnotationType.
186     *
187     *  Any time you wish to see if an Annotation bundle conforms to a
188     *       type
189     * @param ann the Annotation to validate.
190     * @return true if ann conforms to this type and false if it doesn't.
191     */
192    boolean instanceOf(Annotation ann);
193
194    /**
195     * <p>See if an AnnotationType is a specialisation of this type.</p>
196     *
197     * <p>An AnnotationType is a sub-type if it restricts each of the
198     * properties of the super-type to a type that can be cast to the
199     * type in the super-type. Note that this is not always a cast in
200     * the pure Java sense; it may include checks on the number and
201     * type of members in collections or other criteria.</p>
202     *
203     * @param subType an AnnotationType to check.
204     * @return true if subType is a sub-type of this type.
205     *
206     * 
207     */
208    boolean subTypeOf(AnnotationType subType);
209
210    /**
211     * <p>Retrieve the constraint that will be applied to all
212     * properties with a given key.</p>
213     *
214     * <p>For an <code>Annotation</code> to be accepted, each key in
215     * getProperties() must be present in the annotation and each of the
216     * values associated with those properties must match the
217     * constraint.</p>
218     * If you want to find out exactly what constraints will be
219     * applied to a particular propery key
220     *
221     * @param key the property to be validated.
222     * @return PropertyConstraint the constraint by which the values
223     * must be accepted.
224     *
225     */
226    CollectionConstraint getConstraint(Object key);
227
228    /**
229     * Set the constraints associated with a property.  This method constrains
230     * the value of the specified property such that all members must match
231     * <code>con</code>, and the number of members must match <code>card</code>.
232     * It implicitly constructs a <code>CollectionConstraint.AllValuesIn</code>
233     * instance.
234     * <p>When you are building your own AnnotationType</p>
235     * 
236     * @param key  the name of the property to constrain
237     * @param con  the PropertyConstraint to enforce
238     * @param card the CardinalityConstraint to enforce
239     *
240     * 
241     */
242    void setConstraints(
243      Object key,
244      PropertyConstraint con,
245      Location card
246    );
247
248    /**
249     * Specifies the constraint to apply to the specified property.
250     *
251     * @param key    the name of the property to constrain
252     * @param con    the constraint to apply to this slot.
253     *
254     */
255    void setConstraint(
256        Object key,
257        CollectionConstraint con
258    );
259
260    /**
261     * Set the constraints that will apply to all properties without an
262     * explicitly defined set of constraints.  This method constrains
263     * the value of the specified property such that all members must match
264     * <code>con</code>, and the number of members must match <code>card</code>.
265     * It implicitly constructs a <code>CollectionConstraint.AllValuesIn</code>
266     * instance.
267     *
268     * @param pc  the default PropertyConstraint
269     * @param cc the default CardinalityConstraint
270     *
271     */
272    void setDefaultConstraints(PropertyConstraint pc, Location cc);
273
274    /**
275     * Specifies the default constraint to apply to properties where no
276     * other constraint is specified.
277     *
278     * @param cc The default constraint.
279     */
280
281    void setDefaultConstraint(CollectionConstraint cc);
282
283    /**
284     * Get the CollectionConstraint that will be applied to all properties without
285     * an explicit binding. This defaults to CollectionConstraint.ALL.
286     * <p>If you want to find out exactly what constraint will be
287     * applied to properties with no explicitly defined constraints</p>
288     *
289     * @return the default CollectionConstraint
290     *
291     */
292    CollectionConstraint getDefaultConstraint();
293
294    /**
295     * Retrieve the set of properties for which constraints have been explicity specified.
296     * Discover which properties have explicit constraints
297     * 
298     * @return the Set of properties to validate.
299     * 
300     */
301    Set getProperties();
302
303    /**
304     * Set the property in an annotation bundle according to the type we believe
305     * it should be. This will take care of any neccisary packing or unpacking
306     * to Collections.
307     *
308     * @param ann  the Annotation to modify
309     * @param property  the property key Object
310     * @param value  the property value Object
311     * @throws ChangeVetoException  if the value could not be accepted by this
312     *         annotation type for that property key, or if the Annotation could
313     *         not be modified
314     *
315     */
316    void setProperty(Annotation ann, Object property, Object value)
317        throws ChangeVetoException;
318
319    /**
320     * Add a value to the specified property slot.
321     *
322     * @param ann  the Annotation to modify
323     * @param property  the property key Object
324     * @param value  the property value Object
325     * @throws ChangeVetoException  if the value could not be accepted by this
326     *         annotation type for that property key, or if the Annotation could
327     *         not be modified
328     *
329     */
330    void addProperty(Annotation ann, Object property, Object value)
331        throws ChangeVetoException;
332
333    /**
334     * Get the Collection of values associated with an Annotation bundle
335     * according to the type we believe it to be. This will take care of any
336     * neccisary packing or unpacking to Collections. Properties with no values
337     * will return empty Collections.
338     *
339     * @param ann  the Annotation to access
340     * @param property  the property key Object
341     * @return a Collection of values
342     * @throws ChangeVetoException  if the value could not be removed
343     *
344     */
345    Collection getProperty(Annotation ann, Object property)
346        throws ChangeVetoException;
347
348    /**
349     * Remove a value from the specified property slot.
350     *
351     * @param ann  the Annotation to modify
352     * @param property  the property key Object
353     * @param value  the property value Object
354     * @throws ChangeVetoException  if the Annotation could
355     *         not be modified
356     *
357     */
358    void removeProperty(Annotation ann, Object property, Object value)
359        throws ChangeVetoException;
360
361    /**
362     * Set the comment for the whole AnnotationType.
363     * This is human-readable text.
364     *
365     * @param comment  the new comment
366     */
367    void setComment(String comment);
368    
369    /**
370     * Get the comment for the whole AnnotationType.
371     * This is human-readable text.
372     *
373     * @return the comment
374     */
375    String getComment();
376    
377    /**
378     * Set the comment for a particular property.
379     * This is a human-readable description of the property.
380     *
381     * @param property the property to comment on
382     * @param comment  the comment
383     */
384    void setComment(Object property, String comment);
385    
386    /**
387     * Get the comment for a particular property.
388     * This is a human-readable description of the property.
389     *
390     * @param property the property to get a comment for
391     * @return the comment
392     */
393    String getComment(Object property);
394    
395    /**
396     * <p>An abstract base class useful for implementing AnnotationType
397     * instances.</p>
398     *
399     * <p>This provides deffinitions for the logical operators (validate(),
400     * subTypeOf()), the mutators (setProperty(), getProperty() and
401     * deleteProperty()) and toString() that you may not want to
402     * write yourself. It leaves the data-related methods up to you.</p>
403     *
404     * @since 1.3
405     * @author Matthew Pocock
406     * @author Thomas Down
407     *
408     */
409    abstract class Abstract implements AnnotationType {
410        public void setConstraints(Object key, PropertyConstraint pc, Location cc) {
411            setConstraint(key, new CollectionConstraint.AllValuesIn(pc, cc));
412        }
413
414        public void setDefaultConstraints(PropertyConstraint pc, Location cc) {
415            setDefaultConstraint(new CollectionConstraint.AllValuesIn(pc, cc));
416        }
417
418        public boolean instanceOf(Annotation ann) {
419          Set props = new HashSet();
420          props.addAll(getProperties());
421          props.addAll(ann.keys());
422
423          for (Iterator i = getProperties().iterator(); i.hasNext();) {
424            Object key = i.next();
425            CollectionConstraint con = getConstraint(key);
426            Object value;
427            if (ann.containsProperty(key)) {
428                value = ann.getProperty(key);
429            } else {
430                value = Collections.EMPTY_SET;
431            }
432            if (!con.accept(value)) {
433                return false;
434            }
435          }
436
437          return true;
438        }
439
440        public final void setProperty(
441          Annotation ann,
442          Object property,
443          Object value
444        ) throws ChangeVetoException
445        {
446            CollectionConstraint cons = getConstraint(property);
447            if (cons.accept(value)) {
448                ann.setProperty(property, value);
449            } else {
450                throw new ChangeVetoException("Setting property " + property + " to " + value + " would violate constraints in " + cons);
451            }
452        }
453
454        public final Collection getProperty(Annotation ann, Object property)
455        throws ChangeVetoException {
456          Collection vals = null;
457
458          if(!ann.containsProperty(property)) {
459            vals = Collections.EMPTY_SET;
460          } else {
461            Object val = ann.getProperty(property);
462            if(val instanceof Collection) {
463              vals = (Collection) val;
464            } else {
465              vals = Collections.singleton(val);
466            }
467          }
468
469          return vals;
470        }
471
472        public final void addProperty(
473          Annotation ann,
474          Object key,
475          Object value
476        ) throws ChangeVetoException
477        {
478            CollectionConstraint cons = getConstraint(key);
479            Collection oldValue;
480            if (ann.containsProperty(key)) {
481                Object ov = ann.getProperty(key);
482                if (ov instanceof Collection) {
483                    oldValue = (Collection) ov;
484                } else {
485                    oldValue = new ArrayList();
486                    oldValue.add(ov);
487                }
488            } else {
489                oldValue = new ArrayList();
490            }
491            if (cons.validateAddValue(oldValue, value)) {
492                oldValue.add(value);
493                ann.setProperty(key, oldValue);
494            } else {
495                throw new ChangeVetoException("Adding value " + value + " to " + key + " would violate constraints");
496            }
497        }
498
499        public final void removeProperty(
500          Annotation ann,
501          Object key,
502          Object value
503        ) throws ChangeVetoException
504        {
505            CollectionConstraint cons = getConstraint(key);
506            Collection oldValue;
507            if (ann.containsProperty(key)) {
508                Object ov = ann.getProperty(key);
509                if (ov instanceof Collection) {
510                    oldValue = (Collection) ov;
511                } else {
512                    oldValue = new ArrayList();
513                    oldValue.add(ov);
514                }
515            } else {
516                oldValue = new ArrayList();
517            }
518            if (!oldValue.contains(value)) {
519                throw new ChangeVetoException("Property " + key + " does not have value " + value);
520            } else {
521                if (cons.validateRemoveValue(oldValue, value)) {
522                    oldValue.remove(value);
523                    if (oldValue.size() > 0) {
524                        ann.setProperty(key, oldValue);
525                    } else {
526                        ann.removeProperty(key);
527                    }
528                } else {
529                    throw new ChangeVetoException("Adding value " + value + " to " + key + " would violate constraints");
530                }
531            }
532        }
533
534        public String toString() {
535          StringBuffer sb = new StringBuffer(
536            "AnnotationType: " +
537            getComment() +
538            " {"
539          );
540
541          for(Iterator i = getProperties().iterator(); i.hasNext(); ) {
542            Object key = i.next();
543            CollectionConstraint cc = getConstraint(key);
544
545            sb.append(" [" + ", " + cc + " " + key + "]");
546          }
547          sb.append(" [*, " + getDefaultConstraint() + "]");
548
549          sb.append(" }");
550
551          return sb.toString();
552        }
553
554        public boolean subTypeOf(AnnotationType subType) {
555            Set props = new HashSet();
556            props.addAll(getProperties());
557            props.addAll(subType.getProperties());
558
559            for (Iterator i = props.iterator(); i.hasNext();) {
560                Object key = i.next();
561
562                CollectionConstraint thisPC = getConstraint(key);
563                CollectionConstraint subPC = subType.getConstraint(key);
564                if (! thisPC.subConstraintOf(subPC)) {
565                    return false;
566                }
567            }
568
569            return true;
570        }
571    }
572
573    /**
574     * <p>An implementation of <code>AnnotationType</code>.</p>
575     *
576     * <p>To build an instance of <code>AnnotationType.Impl</code>,
577     * first invoke the no-args constructor, and then use the
578     * setPropertyConstraint method to build the property->constraint
579     * mapping.</p>
580     * A convenient class for when you need an AnnotationType
581     * instance and don't want to write your own
582     *
583     * @since 1.3
584     * @author Matthew Pocock
585     * @author Thomas Down
586     *
587     */
588    class Impl extends AnnotationType.Abstract {
589        private Map cons;
590        private CollectionConstraint unknown;
591        private String comment;
592        private Map comments;
593
594        /**
595         * Create a new Impl with no constraints.
596         */
597        public Impl() {
598            cons = new SmallMap();
599            unknown = CollectionConstraint.ANY;
600            comment = "";
601            comments = new SmallMap();
602        }
603
604        /**
605         * Create a new Impl with a default property and cardinality constraint.
606         *
607         * @param defaultPC  the default PropertyConstraint
608         * @param defaultCC  the default CardinalityConstraint
609         */
610        public Impl(PropertyConstraint defaultPC, Location defaultCC) {
611            this();
612            this.setDefaultConstraints(defaultPC, defaultCC);
613        }
614
615        /**
616         * Create a new Impl with a default collection constraint.
617         *
618         * @param unknown  the default CollectionConstraint
619         */
620        public Impl(CollectionConstraint unknown) {
621            this();
622            setDefaultConstraint(unknown);
623        }
624
625        public void setDefaultConstraint(CollectionConstraint cc) {
626            this.unknown = cc;
627        }
628
629        public CollectionConstraint getDefaultConstraint() {
630            return unknown;
631        }
632
633        public CollectionConstraint getConstraint(Object key) {
634            CollectionConstraint pc = (CollectionConstraint) cons.get(key);
635            if (pc == null) {
636                pc = unknown;
637            }
638            return pc;
639        }
640
641        public void setConstraint(
642          Object key,
643          CollectionConstraint cc
644        ) {
645            cons.put(key, cc);
646        }
647
648        public Set getProperties() {
649            return cons.keySet();
650        }
651        
652        public void setComment(String comment) {
653          this.comment = comment;
654        }
655        
656        public String getComment() {
657          return comment;
658        }
659        
660        public void setComment(Object key, String comment) {
661          comments.put(key, comment);
662        }
663        
664        public String getComment(Object key) {
665          return (String) comments.get(key);
666        }
667    }
668}