87ef1ba8ba5c960eab6df829a4834a2c70ce9f2d
[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 private void createTestContents() {
252 // Just for testing:
253 try {
254 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);
255 //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);
256 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);
257
258 // Add test consensus sequence:
259 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
260 int id = consensusModel.addSequence(CONSENSUS_NAME);
261 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
262 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
263 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
264 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
265 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
266 consensusModel.insertTokensAt(id, 0, tokens);
267 }
268 catch (Exception e) {
269 throw new RuntimeException(e);
270 }
271 }
272
273
274 private void readCDMData(Sequence sequenceNode) {
275 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
276
277 // Add reads:
278 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
279 try {
280 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
281 URI uri = null;
282 if (pherogramInfo.getPherogram() != null) {
283 uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
284 }
285 int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
286 uri,
287 singleReadAlignment.isReverseComplement(),
288 singleReadAlignment.getEditedSequence(),
289 singleReadAlignment.getFirstSeqPosition(),
290 singleReadAlignment.getLeftCutPosition(),
291 singleReadAlignment.getRightCutPosition(),
292 singleReadAlignment.getShifts());
293 cdmMap.put(id, singleReadAlignment);
294 }
295 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).
296 MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
297 e.getLocalizedMessage(), "eu.etaxonomy.taxeditor.editor", e, false);
298 }
299 }
300
301 // Set consensus sequence:
302 AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
303 int id = consensusProvider.addSequence(CONSENSUS_NAME);
304 consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
305 sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
306 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
307 }
308
309
310 /* (non-Javadoc)
311 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
312 */
313 @Override
314 public void createPartControl(Composite parent) {
315 SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
316 updateStatusBar();
317
318 if (getEditorInput() instanceof AlignmentEditorInput) {
319 if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
320 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
321 //re-load into the current session if it is already persisted in the DB
322 if(sequenceNode!=null && sequenceNode.getId()!=0){
323 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
324 }
325 readCDMData(sequenceNode);
326 }
327 else {
328 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
329 }
330 }
331 else {
332 throw new IllegalArgumentException("The editor input must have the type " +
333 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
334 }
335 }
336
337
338 private void updateStatusBar() {
339 IActionBars bars = getEditorSite().getActionBars();
340 bars.getStatusLineManager().setMessage("Edit mode: " +
341 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
342 "Insertion in pherogram: " +
343 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
344 }
345
346
347 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
348 SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[model.getShiftChangeCount()];
349 Iterator<ShiftChange> iterator = model.shiftChangeIterator();
350 int pos = 0;
351 while (iterator.hasNext()) {
352 ShiftChange shiftChange = iterator.next();
353 result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
354 }
355 return result;
356 }
357
358
359 /* (non-Javadoc)
360 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
361 */
362 @Override
363 public void doSave(IProgressMonitor monitor) {
364 if (getEditorInput() instanceof AlignmentEditorInput) {
365 String taskName = "Saving alignment";
366 monitor.beginTask(taskName, 3);
367
368 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
369 StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
370
371 // Write consensus sequence:
372 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
373 String newConsensusSequence = stringProvider.getSequence(
374 getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
375 if (consensusSequenceObj == null) {
376 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
377 }
378 else {
379 consensusSequenceObj.setString(newConsensusSequence);
380 }
381
382 // Write single reads:
383 stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
384 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
385 Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
386 while (iterator.hasNext()) {
387 int id = iterator.next();
388 SingleReadAlignment singleRead = cdmMap.get(id);
389 if (singleRead == null) {
390 throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
391 //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?
392 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
393 }
394
395 singleRead.setEditedSequence(stringProvider.getSequence(id));
396
397 PherogramAreaModel model = getPherogramArea(id).getModel();
398 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
399 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
400 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
401 singleRead.setLeftCutPosition(model.getLeftCutPosition());
402 singleRead.setRightCutPosition(model.getRightCutPosition());
403 }
404
405 if (!conversationHolder.isBound()) {
406 conversationHolder.bind();
407 }
408 monitor.worked(1);
409
410 // Commit the conversation and start a new transaction immediately:
411 conversationHolder.commit(true);
412 monitor.worked(1);
413
414 dirty = false;
415 monitor.worked(1);
416 monitor.done();
417 firePropertyChange(PROP_DIRTY);
418 }
419 else {
420 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
421 }
422 }
423
424
425 /* (non-Javadoc)
426 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
427 */
428 @Override
429 public void doSaveAs() {}
430
431
432 /* (non-Javadoc)
433 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
434 */
435 @Override
436 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
437 setSite(site);
438 setInput(input);
439 }
440
441
442 /* (non-Javadoc)
443 * @see org.eclipse.ui.part.EditorPart#isDirty()
444 */
445 @Override
446 public boolean isDirty() {
447 return dirty;
448 }
449
450
451 private void setDirty() {
452 dirty = true;
453 firePropertyChange(IEditorPart.PROP_DIRTY);
454 }
455
456
457 /* (non-Javadoc)
458 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
459 */
460 @Override
461 public boolean isSaveAsAllowed() {
462 return false; // "Save as" not allowed.
463 }
464
465
466 @Override
467 public void setFocus() {
468 if(conversationHolder!=null){
469 conversationHolder.bind();
470 }
471 }
472
473
474 public boolean isInsertMode() {
475 return getAlignmentsContainer().getEditSettings().isInsert();
476 }
477
478
479 public boolean isInsertLeftInPherogram() {
480 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
481 }
482
483
484 public void toggleLeftRightInsertionInPherogram() {
485 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
486 }
487
488
489 public void toggleInsertOverwrite() {
490 getAlignmentsContainer().getEditSettings().toggleInsert();
491 }
492
493
494 private String cutPherogram(boolean left) {
495 SelectionModel selection = getReadsArea().getSelection();
496 if (selection.getCursorHeight() != 1) {
497 return "Cutting pherograms is only possible if exactly one row is selected.";
498 }
499 else {
500 PherogramArea pherogramArea =
501 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
502 if (pherogramArea == null) {
503 return "There is no pherogram attached to the current sequence.";
504 }
505 else {
506 if (left) {
507 if (pherogramArea.setLeftCutPositionBySelection()) {
508 return null;
509 }
510 else {
511 return "The left end of the selection lies outside the pherogram attached to this sequence.";
512 }
513 }
514 else {
515 if (pherogramArea.setRightCutPositionBySelection()) {
516 return null;
517 }
518 else {
519 return "The right end of the selection lies outside the pherogram attached to this sequence.";
520 }
521 }
522 }
523 }
524 }
525
526
527 public String cutPherogramLeft() {
528 return cutPherogram(true);
529 }
530
531
532 public String cutPherogramRight() {
533 return cutPherogram(false);
534 }
535
536
537 public void reverseComplementSelectedSequences() {
538 SelectionModel selection = getReadsArea().getSelection();
539 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
540 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
541 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
542 PherogramArea area = getPherogramArea(sequenceID);
543 PherogramAreaModel pherogramAlignmentModel = area.getModel();
544 AlignmentModelUtils.reverseComplement(model, sequenceID,
545 pherogramAlignmentModel.editableIndexByBaseCallIndex(
546 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
547 pherogramAlignmentModel.editableIndexByBaseCallIndex(
548 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
549 pherogramAlignmentModel.reverseComplement();
550 }
551 }
552
553
554 /**
555 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
556 * sequence is overwritte.
557 */
558 @SuppressWarnings("unchecked")
559 public <T> void createConsensusSequence() {
560 ConsensusSequenceArea area = getConsensusHintDataArea();
561 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
562 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
563 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
564
565 Collection<T> tokens = new ArrayList<T>(length);
566 for (int column = 0; column < length; column++) {
567 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
568 }
569
570 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
571 model.insertTokensAt(sequenceID, 0, tokens);
572 }
573
574
575 /**
576 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
577 * calculated from the single read sequences and extends the consensus sequence if necessary.
578 */
579 @SuppressWarnings("unchecked")
580 public <T> void updateConsensusSequence() {
581 ConsensusSequenceArea area = getConsensusHintDataArea();
582 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
583 TokenSet<T> tokenSet = model.getTokenSet();
584 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
585 int currentConsensusLength = model.getSequenceLength(sequenceID);
586 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
587
588 // Replace gaps by new information:
589 for (int column = 0; column < currentConsensusLength; column++) {
590 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
591 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
592 if (!tokenSet.isGapToken(newToken)) {
593 model.setTokenAt(sequenceID, column, newToken);
594 }
595 }
596 }
597
598 // Append additional tokens:
599 if (overallLength > currentConsensusLength) {
600 Collection<T> tokens = new ArrayList<T>(overallLength);
601 for (int column = currentConsensusLength; column < overallLength; column++) {
602 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
603 }
604 model.appendTokens(sequenceID, tokens);
605 }
606 }
607
608
609 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
610 PherogramProvider result;
611 InputStream stream = uri.toURL().openStream();
612 try {
613 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
614 }
615 finally {
616 stream.close();
617 }
618 return result;
619 }
620
621
622 private String newReadName() {
623 int index = 1;
624 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
625 != AlignmentModel.NO_SEQUENCE_FOUND) {
626
627 index++;
628 }
629 return DEFAULT_READ_NAME_PREFIX + index;
630 }
631
632
633 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
634 addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
635 }
636
637
638 /**
639 * Adds a new sequence with attached phergram data area to the reads alignment.
640 * <p>
641 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
642 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
643 * and the base calls sequence are assumed.
644 *
645 * @param name the name of the new sequence
646 * @param pherogramURI the URI where the associated pherogram file is located
647 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
648 * be added, {@code false} otherwise.
649 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
650 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
651 * @return the sequence ID of the added read
652 * @throws IOException if an error occurred when trying to read the pherogram file
653 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
654 */
655 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
656 Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
657 throws IOException, UnsupportedChromatogramFormatException {
658
659 AlignmentModel provider = getReadsArea().getAlignmentModel();
660 PherogramProvider pherogramProvider = null;
661 if (pherogramURI != null) {
662 pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
663 if (reverseComplemented) {
664 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
665 }
666 }
667
668 // Create sequence:
669 provider.addSequence(name);
670 int id = provider.sequenceIDByName(name);
671
672 // Set edited sequence:
673 Collection<Object> tokens = null; // First save tokens in a collection to avoid GUI updated for each token.
674 if (editedSequence != null) {
675 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
676 }
677 else if (pherogramProvider != null) { // Copy base call sequence into alignment:
678 tokens = new ArrayList<Object>();
679 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
680 tokens.add(provider.getTokenSet().tokenByRepresentation(
681 Character.toString(pherogramProvider.getBaseCall(i))));
682 }
683 setDirty();
684 }
685
686 if (tokens != null) { // If either an edited sequence or a pherogram URI was provided.
687 provider.insertTokensAt(id, 0, tokens);
688 // Create pherogram area:
689 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
690 new PherogramAreaModel(pherogramProvider));
691
692 // Set position properties and shifts:
693 PherogramAreaModel model = pherogramArea.getModel();
694 if ((firstSeqPos != null) && (leftCutPos != null)) {
695 model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
696 }
697 if (rightCutPos != null) {
698 model.setRightCutPosition(rightCutPos);
699 }
700 if ((shifts != null) && (shifts.length > 0)) {
701 for (int i = 0; i < shifts.length; i++) {
702 model.addShiftChange(shifts[i].position, shifts[i].shift);
703 }
704 setDirty();
705 }
706
707 // Add pherogram area to GUI:
708 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
709 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
710 }
711
712 return id;
713 }
714 }