3c1327bb5da8c35f1b4b9057c4fee6bc5c666691
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / molecular / AlignmentEditor.java
1 // $Id$
2 /**
3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.taxeditor.editor.molecular;
11
12
13 import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
14 import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
15 import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
16 import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
17 import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
18 import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
19 import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
20 import info.bioinfweb.libralign.editsettings.EditSettingsListener;
21 import info.bioinfweb.libralign.model.AlignmentModel;
22 import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
23 import info.bioinfweb.libralign.model.AlignmentModelUtils;
24 import info.bioinfweb.libralign.model.adapters.StringAdapter;
25 import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
26 import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
27 import info.bioinfweb.libralign.model.events.TokenChangeEvent;
28 import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
29 import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
30 import info.bioinfweb.libralign.model.tokenset.TokenSet;
31 import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
32 import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
33 import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
34 import info.bioinfweb.libralign.pherogram.model.ShiftChange;
35 import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
36 import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
37 import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
38 import info.bioinfweb.tic.SWTComponentFactory;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.net.URI;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Iterator;
48 import java.util.Map;
49 import java.util.TreeMap;
50
51 import org.biojava.bio.chromatogram.ChromatogramFactory;
52 import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
53 import org.eclipse.core.runtime.IProgressMonitor;
54 import org.eclipse.swt.SWT;
55 import org.eclipse.swt.widgets.Composite;
56 import org.eclipse.ui.IActionBars;
57 import org.eclipse.ui.IEditorInput;
58 import org.eclipse.ui.IEditorPart;
59 import org.eclipse.ui.IEditorSite;
60 import org.eclipse.ui.PartInitException;
61 import org.eclipse.ui.PlatformUI;
62 import org.eclipse.ui.commands.ICommandService;
63 import org.eclipse.ui.part.EditorPart;
64
65 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
66 import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
67 import eu.etaxonomy.cdm.model.media.MediaUtils;
68 import eu.etaxonomy.cdm.model.molecular.Sequence;
69 import eu.etaxonomy.cdm.model.molecular.SequenceString;
70 import eu.etaxonomy.cdm.model.molecular.SingleRead;
71 import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
72 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleInsertOverwriteHandler;
73 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleLeftRightInsertionHandler;
74 import eu.etaxonomy.taxeditor.model.MessagingUtils;
75 import eu.etaxonomy.taxeditor.store.CdmStore;
76 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
77
78
79
80 /**
81 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
82 * a consensus sequence.
83 * <p>
84 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
85 *
86 * @author Ben Stöver
87 * @author pplitzner
88 * @date 04.08.2014
89 */
90 public class AlignmentEditor extends EditorPart {
91 public static final String ID = "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
92
93 public static final int READS_AREA_INDEX = 1;
94 public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
95 public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
96 public static final int PHEROGRAM_AREA_INDEX = 0;
97 public static final int CONSENSUS_DATA_AREA_INDEX = 0;
98 public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
99 public static final String CONSENSUS_NAME = "Consensus";
100
101
102 private final ConversationHolder conversationHolder;
103
104 private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
105 @Override
106 public <T> void afterTokenChange(TokenChangeEvent<T> e) {
107 setDirty();
108 }
109
110 @Override
111 public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
112 setDirty();
113 }
114
115 @Override
116 public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
117 setDirty();
118 }
119
120 @Override
121 public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
122 AlignmentModel<U> newProvider) { // Not expected.
123
124 setDirty();
125 }
126 };
127
128 private MultipleAlignmentsContainer alignmentsContainer = null;
129 private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
130 private boolean dirty = false;
131
132
133 public AlignmentEditor() {
134 super();
135 conversationHolder = CdmStore.createConversation();
136 //conversationHolder = null;
137 }
138
139
140 private void refreshToolbarElement(String id) {
141 ICommandService commandService =
142 (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
143 if (commandService != null) {
144 commandService.refreshElements(id, Collections.EMPTY_MAP);
145 }
146 }
147
148
149 private void registerEditSettingListener(MultipleAlignmentsContainer container) {
150 container.getEditSettings().addListener(new EditSettingsListener() {
151 @Override
152 public void workingModeChanged(EditSettingsChangeEvent e) {} // Currently nothing to do
153
154 @Override
155 public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
156 updateStatusBar();
157 refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
158 }
159
160 @Override
161 public void insertChanged(EditSettingsChangeEvent e) {
162 updateStatusBar();
163 refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
164 }
165 });
166 }
167
168
169 private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
170 AlignmentArea result = new AlignmentArea(container);
171 result.setAllowVerticalScrolling(false);
172 result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
173 return result;
174 }
175
176
177 private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
178 AlignmentArea result = new AlignmentArea(container);
179 result.setAllowVerticalScrolling(allowVerticalScrolling);
180
181 CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance(); //TODO Should NUCLEOTIDE be used instead?
182 AlignmentModel<Character> provider = new PackedAlignmentModel<Character>(tokenSet);
183 result.setAlignmentModel(provider, false);
184 provider.getChangeListeners().add(DIRTY_LISTENER);
185 result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
186
187 return result;
188 }
189
190
191 private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
192 AlignmentArea labeledArea) {
193
194 AlignmentArea result = new AlignmentArea(container);
195 result.setAllowVerticalScrolling(false);
196 result.getDataAreas().getBottomAreas().add(
197 new ConsensusSequenceArea(result.getContentArea(), labeledArea));
198 return result;
199 }
200
201
202 private MultipleAlignmentsContainer getAlignmentsContainer() {
203 if (alignmentsContainer == null) {
204 alignmentsContainer = new MultipleAlignmentsContainer();
205
206 AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
207 AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
208 list.add(createIndexArea(alignmentsContainer, readsArea));
209 list.add(readsArea); // Make sure READS_AREA_INDEX is correct.
210 list.add(createEditableAlignmentArea(alignmentsContainer, false)); // Make sure COMSENSUS_AREA_INDEX is correct.
211 list.add(createConsensusHintArea(alignmentsContainer, readsArea));
212
213 registerEditSettingListener(alignmentsContainer);
214 }
215 return alignmentsContainer;
216 }
217
218
219 public AlignmentArea getReadsArea() {
220 return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
221 }
222
223
224 private AlignmentArea getEditableConsensusArea() {
225 return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
226 }
227
228
229 public boolean hasPherogram(int sequenceID) {
230 return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
231 }
232
233
234 public PherogramArea getPherogramArea(int sequenceID) {
235 if (hasPherogram(sequenceID)) {
236 return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
237 }
238 else {
239 return null;
240 }
241 }
242
243
244 private ConsensusSequenceArea getConsensusHintDataArea() {
245 return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
246 get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
247 get(CONSENSUS_DATA_AREA_INDEX);
248 }
249
250
251 @Deprecated //TODO Remove as soon as testing period is over
252 private void createTestContents() {
253 // Just for testing:
254 try {
255 addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR430_JR-P01.ab1").toURI(), false);
256 //addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR444_JR-P05.ab1").toURI(), false);
257 addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/Test_qualityScore.scf").toURI(), false);
258
259 // Add test consensus sequence:
260 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
261 int id = consensusModel.addSequence(CONSENSUS_NAME);
262 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
263 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
264 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
265 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
266 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
267 consensusModel.insertTokensAt(id, 0, tokens);
268 }
269 catch (Exception e) {
270 throw new RuntimeException(e);
271 }
272 }
273
274
275 private void readCDMData(Sequence sequenceNode) {
276 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
277
278 // Add reads:
279 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
280 try {
281 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
282 URI uri = null;
283 if (pherogramInfo.getPherogram() != null) {
284 uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
285 }
286 int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
287 uri,
288 singleReadAlignment.isReverseComplement(),
289 singleReadAlignment.getEditedSequence(),
290 singleReadAlignment.getFirstSeqPosition(),
291 singleReadAlignment.getLeftCutPosition(),
292 singleReadAlignment.getRightCutPosition(),
293 singleReadAlignment.getShifts());
294 cdmMap.put(id, singleReadAlignment);
295 }
296 catch (Exception e) { // Usually due to an error while trying to read the pherogram (e.g. due to an unsupported format or an invalid URI).
297 MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
298 e.getLocalizedMessage(), "eu.etaxonomy.taxeditor.editor", e, false);
299 }
300 }
301
302 // Set consensus sequence:
303 AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
304 int id = consensusProvider.addSequence(CONSENSUS_NAME);
305 consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
306 sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
307 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
308 }
309
310
311 /* (non-Javadoc)
312 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
313 */
314 @Override
315 public void createPartControl(Composite parent) {
316 SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
317 updateStatusBar();
318
319 if (getEditorInput() instanceof AlignmentEditorInput) {
320 if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
321 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
322 //re-load into the current session if it is already persisted in the DB
323 if(sequenceNode!=null && sequenceNode.getId()!=0){
324 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
325 }
326 readCDMData(sequenceNode);
327 }
328 else {
329 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
330 }
331 }
332 else {
333 throw new IllegalArgumentException("The editor input must have the type " +
334 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
335 }
336 }
337
338
339 private void updateStatusBar() {
340 IActionBars bars = getEditorSite().getActionBars();
341 bars.getStatusLineManager().setMessage("Edit mode: " +
342 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
343 "Insertion in pherogram: " +
344 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
345 }
346
347
348 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
349 SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[model.getShiftChangeCount()];
350 Iterator<ShiftChange> iterator = model.shiftChangeIterator();
351 int pos = 0;
352 while (iterator.hasNext()) {
353 ShiftChange shiftChange = iterator.next();
354 result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
355 }
356 return result;
357 }
358
359
360 /* (non-Javadoc)
361 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
362 */
363 @Override
364 public void doSave(IProgressMonitor monitor) {
365 if (getEditorInput() instanceof AlignmentEditorInput) {
366 String taskName = "Saving alignment";
367 monitor.beginTask(taskName, 3);
368
369 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
370 StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
371
372 // Write consensus sequence:
373 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
374 String newConsensusSequence = stringProvider.getSequence(
375 getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
376 if (consensusSequenceObj == null) {
377 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
378 }
379 else {
380 consensusSequenceObj.setString(newConsensusSequence);
381 }
382
383 // Write single reads:
384 stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
385 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
386 Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
387 while (iterator.hasNext()) {
388 int id = iterator.next();
389 SingleReadAlignment singleRead = cdmMap.get(id);
390 if (singleRead == null) {
391 throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
392 //TODO Create new read object. => Shall it be allowed to add reads in the alignment editor which are not represented in the CDM tree before the alignment editor is saved?
393 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
394 }
395
396 singleRead.setEditedSequence(stringProvider.getSequence(id));
397
398 PherogramAreaModel model = getPherogramArea(id).getModel();
399 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
400 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
401 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
402 singleRead.setLeftCutPosition(model.getLeftCutPosition());
403 singleRead.setRightCutPosition(model.getRightCutPosition());
404 }
405
406 if (!conversationHolder.isBound()) {
407 conversationHolder.bind();
408 }
409 monitor.worked(1);
410
411 // Commit the conversation and start a new transaction immediately:
412 conversationHolder.commit(true);
413 monitor.worked(1);
414
415 dirty = false;
416 monitor.worked(1);
417 monitor.done();
418 firePropertyChange(PROP_DIRTY);
419 }
420 else {
421 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
422 }
423 }
424
425
426 /* (non-Javadoc)
427 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
428 */
429 @Override
430 public void doSaveAs() {}
431
432
433 /* (non-Javadoc)
434 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
435 */
436 @Override
437 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
438 setSite(site);
439 setInput(input);
440 }
441
442
443 /* (non-Javadoc)
444 * @see org.eclipse.ui.part.EditorPart#isDirty()
445 */
446 @Override
447 public boolean isDirty() {
448 return dirty;
449 }
450
451
452 private void setDirty() {
453 dirty = true;
454 firePropertyChange(IEditorPart.PROP_DIRTY);
455 }
456
457
458 /* (non-Javadoc)
459 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
460 */
461 @Override
462 public boolean isSaveAsAllowed() {
463 return false; // "Save as" not allowed.
464 }
465
466
467 @Override
468 public void setFocus() {
469 if(conversationHolder!=null){
470 conversationHolder.bind();
471 }
472 }
473
474
475 public boolean isInsertMode() {
476 return getAlignmentsContainer().getEditSettings().isInsert();
477 }
478
479
480 public boolean isInsertLeftInPherogram() {
481 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
482 }
483
484
485 public void toggleLeftRightInsertionInPherogram() {
486 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
487 }
488
489
490 public void toggleInsertOverwrite() {
491 getAlignmentsContainer().getEditSettings().toggleInsert();
492 }
493
494
495 private String cutPherogram(boolean left) {
496 SelectionModel selection = getReadsArea().getSelection();
497 if (selection.getCursorHeight() != 1) {
498 return "Cutting pherograms is only possible if exactly one row is selected.";
499 }
500 else {
501 PherogramArea pherogramArea =
502 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
503 if (pherogramArea == null) {
504 return "There is no pherogram attached to the current sequence.";
505 }
506 else {
507 if (left) {
508 if (pherogramArea.setLeftCutPositionBySelection()) {
509 return null;
510 }
511 else {
512 return "The left end of the selection lies outside the pherogram attached to this sequence.";
513 }
514 }
515 else {
516 if (pherogramArea.setRightCutPositionBySelection()) {
517 return null;
518 }
519 else {
520 return "The right end of the selection lies outside the pherogram attached to this sequence.";
521 }
522 }
523 }
524 }
525 }
526
527
528 public String cutPherogramLeft() {
529 return cutPherogram(true);
530 }
531
532
533 public String cutPherogramRight() {
534 return cutPherogram(false);
535 }
536
537
538 public void reverseComplementSelectedSequences() {
539 SelectionModel selection = getReadsArea().getSelection();
540 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
541 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
542 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
543 PherogramArea area = getPherogramArea(sequenceID);
544 PherogramAreaModel pherogramAlignmentModel = area.getModel();
545 AlignmentModelUtils.reverseComplement(model, sequenceID,
546 pherogramAlignmentModel.editableIndexByBaseCallIndex(
547 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
548 pherogramAlignmentModel.editableIndexByBaseCallIndex(
549 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
550 pherogramAlignmentModel.reverseComplement();
551 }
552 }
553
554
555 /**
556 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
557 * sequence is overwritte.
558 */
559 @SuppressWarnings("unchecked")
560 public <T> void createConsensusSequence() {
561 ConsensusSequenceArea area = getConsensusHintDataArea();
562 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
563 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
564 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
565
566 Collection<T> tokens = new ArrayList<T>(length);
567 for (int column = 0; column < length; column++) {
568 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
569 }
570
571 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
572 model.insertTokensAt(sequenceID, 0, tokens);
573 }
574
575
576 /**
577 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
578 * calculated from the single read sequences and extends the consensus sequence if necessary.
579 */
580 @SuppressWarnings("unchecked")
581 public <T> void updateConsensusSequence() {
582 ConsensusSequenceArea area = getConsensusHintDataArea();
583 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
584 TokenSet<T> tokenSet = model.getTokenSet();
585 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
586 int currentConsensusLength = model.getSequenceLength(sequenceID);
587 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
588
589 // Replace gaps by new information:
590 for (int column = 0; column < currentConsensusLength; column++) {
591 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
592 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
593 if (!tokenSet.isGapToken(newToken)) {
594 model.setTokenAt(sequenceID, column, newToken);
595 }
596 }
597 }
598
599 // Append additional tokens:
600 if (overallLength > currentConsensusLength) {
601 Collection<T> tokens = new ArrayList<T>(overallLength);
602 for (int column = currentConsensusLength; column < overallLength; column++) {
603 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
604 }
605 model.appendTokens(sequenceID, tokens);
606 }
607 }
608
609
610 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
611 PherogramProvider result;
612 InputStream stream = uri.toURL().openStream();
613 try {
614 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
615 }
616 finally {
617 stream.close();
618 }
619 return result;
620 }
621
622
623 private String newReadName() {
624 int index = 1;
625 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
626 != AlignmentModel.NO_SEQUENCE_FOUND) {
627
628 index++;
629 }
630 return DEFAULT_READ_NAME_PREFIX + index;
631 }
632
633
634 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
635 addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
636 }
637
638
639 /**
640 * Adds a new sequence with attached phergram data area to the reads alignment.
641 * <p>
642 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
643 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
644 * and the base calls sequence are assumed.
645 *
646 * @param name the name of the new sequence
647 * @param pherogramURI the URI where the associated pherogram file is located
648 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
649 * be added, {@code false} otherwise.
650 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
651 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
652 * @return the sequence ID of the added read
653 * @throws IOException if an error occurred when trying to read the pherogram file
654 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
655 */
656 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
657 Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
658 throws IOException, UnsupportedChromatogramFormatException {
659
660 AlignmentModel provider = getReadsArea().getAlignmentModel();
661 PherogramProvider pherogramProvider = null;
662 if (pherogramURI != null) {
663 pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
664 if (reverseComplemented) {
665 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
666 }
667 }
668
669 // Create sequence:
670 provider.addSequence(name);
671 int id = provider.sequenceIDByName(name);
672
673 // Set edited sequence:
674 Collection<Object> tokens = null; // First save tokens in a collection to avoid GUI updated for each token.
675 if (editedSequence != null) {
676 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
677 }
678 else if (pherogramProvider != null) { // Copy base call sequence into alignment:
679 tokens = new ArrayList<Object>();
680 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
681 tokens.add(provider.getTokenSet().tokenByRepresentation(
682 Character.toString(pherogramProvider.getBaseCall(i))));
683 }
684 setDirty();
685 }
686
687 if (tokens != null) { // If either an edited sequence or a pherogram URI was provided.
688 provider.insertTokensAt(id, 0, tokens);
689 // Create pherogram area:
690 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
691 new PherogramAreaModel(pherogramProvider));
692
693 // Set position properties and shifts:
694 PherogramAreaModel model = pherogramArea.getModel();
695 if ((firstSeqPos != null) && (leftCutPos != null)) {
696 model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
697 }
698 if (rightCutPos != null) {
699 model.setRightCutPosition(rightCutPos);
700 }
701 if ((shifts != null) && (shifts.length > 0)) {
702 for (int i = 0; i < shifts.length; i++) {
703 model.addShiftChange(shifts[i].position, shifts[i].shift);
704 }
705 setDirty();
706 }
707
708 // Add pherogram area to GUI:
709 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
710 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
711 }
712
713 return id;
714 }
715 }