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}