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