001/* -*- c-basic-offset: 2; indent-tabs-mode: nil -*- */
002/*
003 *                    BioJava development code
004 *
005 * This code may be freely distributed and modified under the
006 * terms of the GNU Lesser General Public Licence.  This should
007 * be distributed with the code.  If you do not have a copy,
008 * see:
009 *
010 *      http://www.gnu.org/copyleft/lesser.html
011 *
012 * Copyright for this code is held jointly by the individual
013 * authors.  These should be listed in @author doc comments.
014 *
015 * For more information on the BioJava project and its aims,
016 * or to join the biojava-l mailing list, visit the home page
017 * at:
018 *
019 *      http://www.biojava.org/
020 *
021 */
022
023package org.biojava.bio.program.gff;
024
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029import org.biojava.bio.seq.StrandedFeature;
030import org.biojava.utils.SmallMap;
031
032/**
033 * A no-frills implementation of a <span class="type">GFFRecord</span>.
034 *
035 * @author Matthew Pocock
036 * @author Greg Cox
037 * @author Aroul Ramadass
038 * @author Len Trigg
039 * @author Richard Holland
040 */
041public class SimpleGFFRecord implements GFFRecord {
042  /**
043   * The sequence name.
044   */
045  private String seqName;
046  /**
047   * The source.
048   */
049  private String source;
050  /**
051   * The feature type.
052   */
053  private String feature;
054  /**
055   * The start coordinate.
056   */
057  private int start;
058  /**
059   * The end coordinate.
060   */
061  private int end;
062  /**
063   * The feature score.
064   */
065  private double score;
066  /**
067   * The feature strand.
068   */
069  private StrandedFeature.Strand strand;
070  /**
071   * The feature frame.
072   */
073  private int frame;
074  /**
075   * The group-name -> <span class="type">List</span> &lt;attribute&gt;
076   * <span class="type">Map</span>
077   */
078  private Map groupAttributes;
079  /**
080   * The comment.
081   */
082  private String comment;
083
084  /**
085  * Create a new SimpleGFFRecord from GFFRecord object
086  * 
087  * @param rec - A GFFRecord object
088  */
089
090  public SimpleGFFRecord(GFFRecord rec) {
091    this.seqName = rec.getSeqName();
092    this.source = rec.getSource();
093    this.feature = rec.getFeature();
094    this.start = rec.getStart();
095    this.end = rec.getEnd();
096    this.score = rec.getScore();
097    this.strand = rec.getStrand();
098    this.frame = rec.getFrame();
099    this.comment = rec.getComment();
100    this.groupAttributes = new SmallMap(rec.getGroupAttributes());
101  }
102
103  public SimpleGFFRecord(
104                         String seqName,
105                         String source,
106                         String feature,
107                         int start,
108                         int end,
109                         double score,
110                         StrandedFeature.Strand strand,
111                         int frame,
112                         String comment,
113                         Map groupAttributes
114                         ) {
115    this.seqName = seqName;
116    this.source = source;
117    this.feature = feature;
118    this.start = start;
119    this.end = end;
120    this.score = score;
121    this.strand = strand;
122    this.frame = frame;
123    this.comment = comment;
124    this.groupAttributes = new SmallMap(groupAttributes);
125  }
126
127
128   /**
129   * Create a new SimpleGFFRecord with values set to null or zero
130   */
131  public SimpleGFFRecord() {
132    this.seqName = null;
133    this.source = null;
134    this.feature = null;
135    this.start = 0;
136    this.end = 0;
137    this.score = 0;
138    this.strand = null;
139    this.frame = 0;
140    this.comment = null;
141    this.groupAttributes = null;
142  }
143
144  /**
145   * Set the sequence name to <span class="arg">seqName</span>.
146   *
147   * @param seqName  the new name
148   */
149  public void setSeqName(String seqName) {
150    this.seqName = seqName;
151  }
152
153  public String getSeqName() {
154    return seqName;
155  }
156
157  /**
158   * Set the feature source to <span class="arg">source</source>.
159   *
160   * @param source  the new source
161   */
162  public void setSource(String source) {
163    this.source = source;
164  }
165
166  public String getSource() {
167    return source;
168  }
169
170  /**
171   * Set the feature type to <span class="arg">type</source>.
172   *
173   * @param feature  the new feature type
174   */
175  public void setFeature(String feature) {
176    this.feature = feature;
177  }
178
179  public String getFeature() {
180    return feature;
181  }
182
183  /**
184   * Set the start coordinate to <span class="arg">start</source>.
185   *
186   * @param start  the new start coordinate
187   */
188  public void setStart(int start) {
189    this.start = start;
190  }
191
192  public int getStart() {
193    return start;
194  }
195
196  /**
197   * Set the end coordinate to <span class="arg">end</source>.
198   *
199   * @param end  the new end coordinate
200   */
201  public void setEnd(int end) {
202    this.end = end;
203  }
204
205  public int getEnd() {
206    return end;
207  }
208
209  /**
210   * Set the score to <span class="arg">score</source>.
211   * <p>
212   * The score must be a double, inclusive of <code>0</code>.
213   * If you wish to indicate that there is no score, then use
214   * <span class="type">GFFRecord</span>.<span class="const">NO_SCORE</span>.
215   *
216   * @param score  the new score
217   */
218  public void setScore(double score) {
219    this.score = score;
220  }
221
222  public double getScore() {
223    return score;
224  }
225
226  /**
227   * Set the strand to <span class="arg">strand</source>.
228   *
229   * @param strand the new Strand
230   */
231  public void setStrand(StrandedFeature.Strand strand) {
232    this.strand = strand;
233  }
234
235  public StrandedFeature.Strand getStrand() {
236    return strand;
237  }
238
239  /**
240   * Set the frame to <span class="arg">frame</source>.
241   * <p>
242   * The score must be  one of <code>{0, 1, 2}</code> or
243   * <span class="type">GFFRecord</span>.<span class="const">NO_FRAME</span>.
244   *
245   * @param frame the frame
246   * @throws IllegalArgumentException if score is not valid.
247   */
248  public void setFrame(int frame) {
249    if (frame != GFFTools.NO_FRAME &&
250       (frame < 0 || frame > 2))
251    {
252      throw new IllegalArgumentException("Illegal frame: " + frame);
253    }
254    this.frame = frame;
255  }
256
257  public int getFrame() {
258    return frame;
259  }
260
261  /**
262   * Replace the group-attribute <span class="type">Map</span> with
263   * <span class="arg">ga</span>.
264   * <p>
265   * To efficiently add a key, call <span class="method">getGroupAttributes()</span>
266   * and modify the <span class="type">Map</span>.
267   *
268   * @param ga  the new group-attribute <span class="type">Map</span>
269   */
270  public void setGroupAttributes(Map ga) {
271    this.groupAttributes = ga;
272  }
273
274  public Map getGroupAttributes() {
275    if (groupAttributes == null) {
276      groupAttributes = new SmallMap();
277    }
278    return groupAttributes;
279  }
280
281  /**
282   * Set the comment to <span class="arg">comment</source>.
283   * <p>
284   * If you set it to null, then the comment for this line will be ignored.
285   *
286   * @param comment the new comment
287   */
288  public void setComment(String comment) {
289    this.comment = comment;
290  }
291
292  public String getComment() {
293    return comment;
294  }
295
296  /**
297   * Create a <span class="type">String</span> representation of
298   * <span class="arg">attMap</span>.
299   *
300   * <span class="arg">attMap</span> is assumed to contain
301   * <span class="type">String</span> keys and
302   * <span class="type">List</span> values.
303   *
304   * @param attMap  the <span class="type">Map</span> of attributes and value lists
305   * @return  a GFF attribute/value <span class="type">String</span>
306   */
307  public static String stringifyAttributes(Map attMap) {
308    StringBuffer sBuff = new StringBuffer();
309    Iterator ki = attMap.keySet().iterator();
310    while (ki.hasNext()) {
311      String key = (String) ki.next();
312      sBuff.append(key);
313      List values = (List) attMap.get(key);
314      for (Iterator vi = values.iterator(); vi.hasNext();) {
315        String value = (String) vi.next();
316        if (isText(value)) {
317          sBuff.append(" \"" + value + "\"");
318        } else {
319          sBuff.append(" " + value);
320        }
321      }
322      if (ki.hasNext()) {
323        sBuff.append(" ;");
324      }
325    }
326    return sBuff.substring(0);
327  }
328
329  /**
330   * Returns true if a string is "textual". The GFF Spec says that
331   * "textual" values must be quoted. This implementation just tests
332   * if the string contains letters or whitespace.
333   *
334   * @param value a <code>String</code> value.
335   * @return true if value is "textual".
336   */
337  private static boolean isText(String value) {
338    for (int i = 0; i < value.length(); i++) {
339      char c = value.charAt(i);
340      if (Character.isLetter(c) || Character.isWhitespace(c)) {
341        return true;
342      }
343    }
344    return false;
345  }
346}
347