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 */
021package org.biojava.bio.seq;
022
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.NoSuchElementException;
027import java.util.Set;
028
029import org.biojava.bio.Annotatable;
030import org.biojava.bio.Annotation;
031import org.biojava.bio.SimpleAnnotation;
032import org.biojava.utils.AbstractChangeable;
033import org.biojava.utils.ChangeSupport;
034import org.biojava.utils.ChangeType;
035import org.biojava.utils.SmallMap;
036
037/**
038 * Registry of known types of features.
039 *
040 * <p>Historically, the type of a feature within a sequence has been represented
041 * by the class or interface of the feature, or by the type property. Different
042 * databases use different names for types, and the same name can mean different
043 * things depending on where it came from.
044 * </p>
045 *
046 * <p>This class attempts to provide a framework for registering and managing
047 * feature types. It includes the concept of a type having a FeatureFilter
048 * schema that would match all examples of that type, and a URI for that type.
049 * Groups of related types, such as all those defined in embl, can be packaged
050 * up into a single namespace. One type can refer to any number of others,
051 * indicating that they are direct parents. This lets us say things like an
052 * Ensembl exon is a specific type of the general exon type, and also an
053 * instance of the Ensembl feature type.</p>
054 *
055 * <p>All feature types are presumed to have URIs that are of the form:
056 * <code>uri:biojava.org:types:${repository}/${type}</code> where
057 * <code>${repository}</code> is something like embl or ensembl. Feature types
058 * defined within biojava will be in the repository called "core".
059 * <code>${type}</code> is the local type of the feature. This will be something
060 * like exon, or repeat. The total URI must be unique. Different repositories
061 * can (and are encouraged to) define types with the same names.</p>
062 *
063 * @author Matthew Pocock
064 * @since 1.3
065 */
066public class FeatureTypes {
067  private static final Map repositories;
068  
069  /** The standard prefix for all type URIs **/
070  public static final String URI_PREFIX = "uri:biojava.org:types";
071  
072  static {
073    repositories = new SmallMap();
074  }
075  
076  /**
077   * <p>Fetch a repository by name.</p>
078   *
079   * <p>Use this to find out what types a repository can provide.</p>
080   *
081   * @param name  the name of the Repository to fetch
082   * @return the Repository with that name
083   * @throws NoSuchElementException  if there is no repository by that name
084   */
085  public static Repository getRepository(String name)
086  throws NoSuchElementException {
087    Repository rep = (Repository) repositories;
088    
089    if(rep == null) {
090      throw new NoSuchElementException("Could not find repository: " + name);
091    }
092    
093    return rep;
094  }
095  
096  /**
097   * Find the names of all known repositories.
098   *
099   * @return a Set of all repository names
100   */
101  public static Set getRepositoryNames() {
102    return repositories.keySet();
103  }
104  
105  /**
106   * Add a repository to FeatureTypes.
107   *
108   * @param repos  the Repository to add
109   */
110  public static void addRepository(Repository repos) {
111    repositories.put(repos.getName(), repos);
112  }
113  
114  /**
115   * Remove a repository from FeaturTypes.
116   *
117   * @param repos  the Repository to remove
118   */
119  public static void removeRepository(Repository repos) {
120    repositories.remove(repos.getName());
121  }
122  
123  /**
124   * <p>Get a Type by URI.</p>
125   *
126   * <p>This will attemt to resolve the URI to a type by first matching the
127   * standard prefix, then finding a repository of the right name and finally
128   * searching that with the type-name portion of the URI.</p>
129   *
130   * @param uri  the URI to resolve
131   * @return a Type with that URI
132   * @throws NoSuchElementException if the type could not be resolved
133   */
134  public static Type getType(String uri) {
135    if(!uri.startsWith(URI_PREFIX)) {
136      throw new NoSuchElementException(
137        "All types start with: " + URI_PREFIX +
138        " while processing " + uri
139      );
140    }
141    
142    String names = uri.substring(URI_PREFIX.length() + 1);
143    int slash = uri.indexOf("/");
144    String repName = names.substring(0, slash);
145    String typeName = names.substring(slash + 1);
146    
147    Repository rep = getRepository(repName);
148    return rep.getType(typeName);
149  }
150  
151  /**
152   * <p>Work out if one type is a sub-type of another.</p>
153   *
154   * <p>This is the transiative closure of Type.getParent(). It will return true
155   * if superType is reachable by following getParent() calls, and false
156   * otherwise.</p>
157   *
158   * @param subType  the Type of the potential sub type
159   * @param superType  the Type of the potential super type
160   * @return true if they are sub-super types, false otherwise
161   */
162  public static boolean isSubTypeOf(Type subType, Type superType) {
163    Set parents = subType.getParents();
164    
165    if(parents.contains(superType.getURI())) {
166      return true;
167    }
168    
169    for(Iterator i = parents.iterator(); i.hasNext(); ) {
170      String puri = (String) i.next();
171      return isSubTypeOf(getType(puri), superType);
172    }
173    
174    return false;
175  }
176  
177  /**
178   * A named collection of Types.
179   *
180   * @author Matthew Pocock
181   * @since 1.3
182   */
183  public static interface Repository
184  extends Annotatable {
185    /**
186     * <p>The name of this repository.</p>
187     *
188     * <p>This will be the ${repository} component of any URIs of types defined
189     * here.</p>
190     *
191     * @return the name of the repository
192     */
193    String getName();
194    
195    /**
196     * Get a set of all type names defined in this repository.
197     *
198     * @return a Set of Type names as Strings
199     */
200    Set getTypes();
201    
202    /**
203     * Find the type for a name.
204     *
205     * @param name  the name of the Type
206     * @return  the Type of that name
207     * @throws NoSuchElementException  if that type can not be found
208     */
209    Type getType(String name)
210    throws NoSuchElementException;
211  }
212  
213  /**
214   * A type of feature.
215   *
216   * @author Matthew Pocock
217   * @since 1.3
218   */
219  public static interface Type
220  extends Annotatable {
221    /**
222     * <p>Get the schema for this type.</p>
223     *
224     * <p>The schema is represented as a FeatureFilter. This will almost
225     * certainly be a complext filter using ands and ors to combine multiple
226     * constraints. A particular type may chose to restrict any one of the
227     * feature's properties, their allowed children and their allowed parents
228     * in a feature hierachy, the type of the annotation associated with it and
229     * anything else that can be expressed using a feature fitler.</p>
230     *
231     * <p>For a feature to actualy conform to this type, it must be acceptable
232     * by the schema filter.</p>
233     *
234     * @return the schema FeatureFilter
235     */
236    FeatureFilter getSchema();
237    
238    /**
239     * Get the name of this type.
240     *
241     * @return the Type name
242     */
243    String getName();
244    
245    /**
246     * Get a set of URIs for parent types.
247     *
248     * @return a Set of all parent URIs
249     */
250    Set getParents();
251    
252    /**
253     * <p>Get the URI for this type.</p>
254     *
255     * <p>The URI will be composed according to the rules defined in
256     * FeatureTypes, being of the form
257     * <code>uri:biojava.org:types:${repository}/${type}</code>.</p>
258     *
259     * @return the URI for this type
260     */
261    String getURI();
262  }
263  
264  /**
265   * A simple implementation of a Repository.
266   *
267   * @author Matthew Pocock
268   * @since 1.3
269   */
270  public static class RepositoryImpl
271  extends AbstractChangeable
272  implements Repository {
273    private final String name;
274    private final Map types;
275    private final Annotation ann;
276    
277    /**
278     * Create a named repository.
279     *
280     * @param name the name of this repository
281     */
282    public RepositoryImpl(String name) {
283      this.name = name;
284      types = new HashMap();
285      this.ann = new SimpleAnnotation();
286    }
287    
288    protected ChangeSupport getChangeSupport(ChangeType ct) {
289      ChangeSupport cs = super.getChangeSupport(ct);
290      ann.addChangeListener(new Annotatable.AnnotationForwarder(this, cs), Annotation.PROPERTY);
291      return cs;
292    }
293    
294    public Annotation getAnnotation() {
295      return ann;
296    }
297    
298    public String getName() {
299      return name;
300    }
301    
302    public Set getTypes() {
303      return types.keySet();
304    }
305    
306    public Type getType(String name)
307    throws NoSuchElementException {
308      Type type = (Type) types.get(name);
309      if(type == null) {
310        throw new NoSuchElementException(
311          "Could not find type " + name +
312          " in repository " + getName()
313        );
314      }
315      return type;
316    }
317    
318    /**
319     * Create a new type in this repository.
320     *
321     * @param name  the Type name
322     * @param schema  the FeatureFilter defining the type
323     * @param parents  the Set (possibly empty) of parent URIs
324     */
325    public Type createType(
326      String name,
327      FeatureFilter schema,
328      Set parents
329    ) {
330      Type type = new TypeImpl(name, schema, parents);
331      types.put(name, type);
332      return type;
333    }
334    
335    private class TypeImpl
336    extends AbstractChangeable
337    implements Type {
338      private Annotation ann;
339      private String name;
340      private FeatureFilter schema;
341      private Set parents;
342      
343      public TypeImpl(
344        String name,
345        FeatureFilter schema,
346        Set parents
347      ) {
348        this.name = name;
349        this.schema = schema;
350        this.parents = parents;
351        this.ann = new SimpleAnnotation();
352      }
353      
354      protected ChangeSupport getChangeSupport(ChangeType ct) {
355        ChangeSupport cs = super.getChangeSupport(ct);
356        ann.addChangeListener(new Annotatable.AnnotationForwarder(this, cs), Annotation.PROPERTY);
357        return cs;
358      }
359      
360      public Annotation getAnnotation() {
361        return ann;
362      }
363      
364      public String getName() {
365        return name;
366      }
367      
368      public FeatureFilter getSchema() {
369        return schema;
370      }
371      
372      public Set getParents() {
373        return parents;
374      }
375      
376      public String getURI() {
377        return URI_PREFIX + "/" + RepositoryImpl.this.getName() + "/" + name;
378      }
379    }
380  }
381}