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