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.nbio.core.sequence.edits; 022 023import org.biojava.nbio.core.exceptions.CompoundNotFoundException; 024import org.biojava.nbio.core.sequence.BasicSequence; 025import org.biojava.nbio.core.sequence.storage.JoiningSequenceReader; 026import org.biojava.nbio.core.sequence.template.Compound; 027import org.biojava.nbio.core.sequence.template.Sequence; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import java.util.ArrayList; 032import java.util.List; 033 034/** 035 * Interface for carrying out edit operations on a Sequence. The 3 major 036 * methods of Editing are supported 037 * 038 * <ul> 039 * <li>Insertion</li> 040 * <li>Deletion</li> 041 * <li>Substitution</li> 042 * </ul> 043 * 044 * The interface is provided so end users can use our implementations, which 045 * are implementations which attempts to create views of Sequences in an 046 * editted form not a full-realised editted Sequence, or their own. 047 * 048 * @author ayates 049 * @param <C> The type of compound to edit 050 */ 051public interface Edit<C extends Compound> { 052 053 Sequence<C> edit(Sequence<C> sequence); 054 055 /** 056 * Abstract class which defines all edit operations as a call to discover 057 * what 5' and 3' ends of an editing Sequence should be joined together 058 * with a target Sequence. These ends can be of 0 length but conceptionally 059 * they can still exist. 060 */ 061 public static abstract class AbstractEdit<C extends Compound> implements Edit<C> { 062 063 private final static Logger logger = LoggerFactory.getLogger(AbstractEdit.class); 064 065 /** 066 * Should return the 5-prime end of the given Sequence according to 067 * the edit. An empty Sequence is valid. 068 */ 069 protected abstract Sequence<C> getFivePrime(Sequence<C> editingSequence); 070 071 /** 072 * Should return the 3-prime end of the given Sequence according to 073 * the edit. An empty Sequence is valid. 074 */ 075 protected abstract Sequence<C> getThreePrime(Sequence<C> editingSequence); 076 077 078 @Override 079 public Sequence<C> edit(Sequence<C> editingSequence) { 080 Sequence<C> targetSequence = getTargetSequence(editingSequence); 081 List<Sequence<C>> sequences = new ArrayList<Sequence<C>>(); 082 083 sequences.add(getFivePrime(editingSequence)); 084 sequences.add(targetSequence); 085 sequences.add(getThreePrime(editingSequence)); 086 087 return new JoiningSequenceReader<C>(sequences); 088 } 089 private int start = -1; 090 private int end = -1; 091 private String stringSequence; 092 private Sequence<C> sequence; 093 094 public AbstractEdit(int start) { 095 this.start = start; 096 } 097 098 public AbstractEdit(int start, int end) { 099 this.start = start; 100 this.end = end; 101 } 102 103 protected void setStringSequence(String stringSequence) { 104 this.stringSequence = stringSequence; 105 } 106 107 protected void setSequence(Sequence<C> sequence) { 108 this.sequence = sequence; 109 } 110 111 /** 112 * Returns the Sequence which is our edit. 113 * 114 * @param editingSequence Asked for in-case we need to do String to 115 * Sequence conversion so we need a CompoundSet which is given 116 * by the Sequence we are editing 117 * @return The Sequence<C> object we wish to insert 118 */ 119 public Sequence<C> getTargetSequence(Sequence<C> editingSequence) { 120 if (sequence == null && stringSequence != null) { 121 try { 122 sequence = new BasicSequence<C>( 123 stringSequence, editingSequence.getCompoundSet()); 124 } catch (CompoundNotFoundException e) { 125 // TODO is there a better way to handle this exception? 126 logger.error("Problem setting sequence, some unrecognised compounds: {}", e.getMessage()); 127 } 128 } 129 return sequence; 130 } 131 132 /** 133 * Returns an empty sequence with the given compound set of the editing 134 * sequence 135 */ 136 protected Sequence<C> getEmptySequence(Sequence<C> editingSequence) { 137 Sequence<C> s = null; 138 try { 139 s = new BasicSequence<C>("", editingSequence.getCompoundSet()); 140 } catch (CompoundNotFoundException e) { 141 // should not happen 142 logger.error("Could not construct empty sequence. {}. This is most likely a bug.", e.getMessage()); 143 } 144 return s; 145 } 146 147 public int getStart() { 148 return start; 149 } 150 151 public int getEnd() { 152 return end; 153 } 154 } 155 156 /** 157 * Implementation which allows for the deletion of bases from a Sequence 158 */ 159 public static class Delete<C extends Compound> extends AbstractEdit<C> { 160 161 public Delete(int position) { 162 this(position, position); 163 } 164 165 public Delete(int start, int end) { 166 super(start, end); 167 setStringSequence(""); 168 } 169 170 protected int getRealStart() { 171 return getStart() - 1; 172 } 173 174 protected int getRealEnd() { 175 return getEnd() + 1; 176 } 177 178 @Override 179 protected Sequence<C> getFivePrime(Sequence<C> editingSequence) { 180 int start = getRealStart(); 181 if (start == 0) { 182 return getEmptySequence(editingSequence); 183 } 184 return editingSequence.getSubSequence(1, start); 185 } 186 187 @Override 188 protected Sequence<C> getThreePrime(Sequence<C> editingSequence) { 189 int end = getRealEnd(); 190 if (end > editingSequence.getLength()) { 191 return getEmptySequence(editingSequence); 192 } 193 return editingSequence.getSubSequence(end, editingSequence.getLength()); 194 } 195 } 196 197 /** 198 * Edit implementation which allows us to insert a base at any position 199 * in a Sequence. Specifying 1 base is used to insert at the start and 200 * end of a Sequence. If you wish to carry out an in-sequence insertion 201 * then you specify the flanking base positions e.g. 202 * 203 * <pre> 204 * ACTG insert TT @ position 1 : TTACGT 205 * ACTG insert TT @ position 2,3 : ACTTGT 206 * ACTG insert A @ position 4 : ACGTA 207 * </pre> 208 * 209 * The code will raise exceptions if you attempt a single base edit 210 * with an insertion. 211 */ 212 public static class Insert<C extends Compound> extends AbstractEdit<C> { 213 214 private final boolean singlePosition; 215 216 public Insert(String sequence, int position) { 217 super(position, position); 218 this.singlePosition = true; 219 setStringSequence(sequence); 220 } 221 222 public Insert(Sequence<C> sequence, int position) { 223 super(position, position); 224 this.singlePosition = true; 225 setSequence(sequence); 226 } 227 228 public Insert(String sequence, int start, int stop) { 229 super(start, stop); 230 this.singlePosition = false; 231 setStringSequence(sequence); 232 } 233 234 public Insert(Sequence<C> sequence, int start, int stop) { 235 super(start, stop); 236 this.singlePosition = false; 237 setSequence(sequence); 238 } 239 240 @Override 241 protected Sequence<C> getFivePrime(Sequence<C> editingSequence) { 242 if (singlePosition) { 243 if (getStart() == 1) { 244 return getEmptySequence(editingSequence); 245 } else if (getEnd() == editingSequence.getLength()) { 246 return editingSequence; 247 } else { 248 throw new IllegalStateException("Given one position to " 249 + "insert at but this is not the start or end " 250 + "of the Sequence; cannot support this"); 251 } 252 } 253 return editingSequence.getSubSequence(1, getStart()); 254 } 255 256 @Override 257 protected Sequence<C> getThreePrime(Sequence<C> editingSequence) { 258 if (singlePosition) { 259 if (getStart() == 1) { 260 return editingSequence; 261 } else if (getEnd() == editingSequence.getLength()) { 262 return getEmptySequence(editingSequence); 263 } else { 264 throw new IllegalStateException("Given one position to " 265 + "insert at but this is not the start or end " 266 + "of the Sequence; cannot support this"); 267 } 268 } 269 return editingSequence.getSubSequence(getEnd(), editingSequence.getLength()); 270 } 271 } 272 273 /** 274 * Allows for the substitution of bases into an existing Sequence. This 275 * allows us to do edits like: 276 * 277 * <pre> 278 * Sub TT @ position 2 279 * AAAA -> ATTA 280 * </pre> 281 * 282 * We do not support 283 * 284 * Edits do not require the length of the insertion but do rely on the 285 * presence of a CompoundSet to parse a String (if given) which means 286 * the eventual length of a Sequence is a lazy operation. 287 */ 288 public static class Substitute<C extends Compound> extends AbstractEdit<C> { 289 290 public Substitute(String sequence, int position) { 291 super(position); 292 setStringSequence(sequence); 293 } 294 295 public Substitute(Sequence<C> sequence, int position) { 296 super(position); 297 setSequence(sequence); 298 } 299 300 /** 301 * Must use this rather than the no-args getEnd as this can return 302 * -1 and the length of a sub is dependent on the length of the 303 * Sequence; we cannot assume 1:1 mapping between characters in a 304 * String and the number of compounds we will have to insert. 305 */ 306 public int getEnd(Sequence<C> sequence) { 307 if (getEnd() == -1) { 308 int start = getStart(); 309 int length = getTargetSequence(sequence).getLength(); 310 return (start + length) - 1; 311 } 312 return getEnd(); 313 } 314 315 @Override 316 protected Sequence<C> getFivePrime(Sequence<C> editingSequence) { 317 int start = getStart(); 318 if (start == 1) { 319 return getEmptySequence(editingSequence); 320 } 321 return editingSequence.getSubSequence(1, start - 1); 322 } 323 324 @Override 325 protected Sequence<C> getThreePrime(Sequence<C> editingSequence) { 326 int end = getEnd(editingSequence); 327 if (end > editingSequence.getLength()) { 328 throw new IndexOutOfBoundsException(end + 329 " is greater than the max index of " + 330 "the editing sequence (" + 331 editingSequence.getLength()); 332 } else if (end == editingSequence.getLength()) { 333 return getEmptySequence(editingSequence); 334 } 335 return editingSequence.getSubSequence(end + 1, editingSequence.getLength()); 336 } 337 } 338}