Minor changes.
[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 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(), TaxeditorMolecularPlugin.PLUGIN_ID, 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 PherogramArea pherogramArea = getPherogramArea(id);
401 if (pherogramArea != null) {
402 PherogramAreaModel model = pherogramArea.getModel();
403 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
404 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
405 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
406 singleRead.setLeftCutPosition(model.getLeftCutPosition());
407 singleRead.setRightCutPosition(model.getRightCutPosition());
408 }
409 }
410
411 if (!conversationHolder.isBound()) {
412 conversationHolder.bind();
413 }
414 monitor.worked(1);
415
416 // Commit the conversation and start a new transaction immediately:
417 conversationHolder.commit(true);
418 monitor.worked(1);
419
420 dirty = false;
421 monitor.worked(1);
422 monitor.done();
423 firePropertyChange(PROP_DIRTY);
424 }
425 else {
426 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
427 }
428 }
429
430
431 /* (non-Javadoc)
432 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
433 */
434 @Override
435 public void doSaveAs() {}
436
437
438 /* (non-Javadoc)
439 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
440 */
441 @Override
442 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
443 setSite(site);
444 setInput(input);
445 }
446
447
448 /* (non-Javadoc)
449 * @see org.eclipse.ui.part.EditorPart#isDirty()
450 */
451 @Override
452 public boolean isDirty() {
453 return dirty;
454 }
455
456
457 private void setDirty() {
458 dirty = true;
459 firePropertyChange(IEditorPart.PROP_DIRTY);
460 }
461
462
463 /* (non-Javadoc)
464 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
465 */
466 @Override
467 public boolean isSaveAsAllowed() {
468 return false; // "Save as" not allowed.
469 }
470
471
472 @Override
473 public void setFocus() {
474 if(conversationHolder!=null){
475 conversationHolder.bind();
476 }
477 }
478
479
480 public boolean isInsertMode() {
481 return getAlignmentsContainer().getEditSettings().isInsert();
482 }
483
484
485 public boolean isInsertLeftInPherogram() {
486 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
487 }
488
489
490 public void toggleLeftRightInsertionInPherogram() {
491 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
492 }
493
494
495 public void toggleInsertOverwrite() {
496 getAlignmentsContainer().getEditSettings().toggleInsert();
497 }
498
499
500 private String cutPherogram(boolean left) {
501 SelectionModel selection = getReadsArea().getSelection();
502 if (selection.getCursorHeight() != 1) {
503 return "Cutting pherograms is only possible if exactly one row is selected.";
504 }
505 else {
506 PherogramArea pherogramArea =
507 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
508 if (pherogramArea == null) {
509 return "There is no pherogram attached to the current sequence.";
510 }
511 else {
512 if (left) {
513 if (pherogramArea.setLeftCutPositionBySelection()) {
514 return null;
515 }
516 else {
517 return "The left end of the selection lies outside the pherogram attached to this sequence.";
518 }
519 }
520 else {
521 if (pherogramArea.setRightCutPositionBySelection()) {
522 return null;
523 }
524 else {
525 return "The right end of the selection lies outside the pherogram attached to this sequence.";
526 }
527 }
528 }
529 }
530 }
531
532
533 public String cutPherogramLeft() {
534 return cutPherogram(true);
535 }
536
537
538 public String cutPherogramRight() {
539 return cutPherogram(false);
540 }
541
542
543 public void reverseComplementSelectedSequences() {
544 SelectionModel selection = getReadsArea().getSelection();
545 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
546 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
547 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
548 PherogramArea area = getPherogramArea(sequenceID);
549 PherogramAreaModel pherogramAlignmentModel = area.getModel();
550 AlignmentModelUtils.reverseComplement(model, sequenceID,
551 pherogramAlignmentModel.editableIndexByBaseCallIndex(
552 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
553 pherogramAlignmentModel.editableIndexByBaseCallIndex(
554 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
555 pherogramAlignmentModel.reverseComplement();
556 }
557 }
558
559
560 /**
561 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
562 * sequence is overwritte.
563 */
564 @SuppressWarnings("unchecked")
565 public <T> void createConsensusSequence() {
566 ConsensusSequenceArea area = getConsensusHintDataArea();
567 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
568 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
569 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
570
571 Collection<T> tokens = new ArrayList<T>(length);
572 for (int column = 0; column < length; column++) {
573 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
574 }
575
576 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
577 model.insertTokensAt(sequenceID, 0, tokens);
578 }
579
580
581 /**
582 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
583 * calculated from the single read sequences and extends the consensus sequence if necessary.
584 */
585 @SuppressWarnings("unchecked")
586 public <T> void updateConsensusSequence() {
587 ConsensusSequenceArea area = getConsensusHintDataArea();
588 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
589 TokenSet<T> tokenSet = model.getTokenSet();
590 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
591 int currentConsensusLength = model.getSequenceLength(sequenceID);
592 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
593
594 // Replace gaps by new information:
595 for (int column = 0; column < currentConsensusLength; column++) {
596 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
597 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
598 if (!tokenSet.isGapToken(newToken)) {
599 model.setTokenAt(sequenceID, column, newToken);
600 }
601 }
602 }
603
604 // Append additional tokens:
605 if (overallLength > currentConsensusLength) {
606 Collection<T> tokens = new ArrayList<T>(overallLength);
607 for (int column = currentConsensusLength; column < overallLength; column++) {
608 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
609 }
610 model.appendTokens(sequenceID, tokens);
611 }
612 }
613
614
615 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
616 PherogramProvider result;
617 InputStream stream = uri.toURL().openStream();
618 try {
619 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
620 }
621 finally {
622 stream.close();
623 }
624 return result;
625 }
626
627
628 private String newReadName() {
629 int index = 1;
630 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
631 != AlignmentModel.NO_SEQUENCE_FOUND) {
632
633 index++;
634 }
635 return DEFAULT_READ_NAME_PREFIX + index;
636 }
637
638
639 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
640 addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
641 }
642
643
644 /**
645 * Adds a new sequence with attached phergram data area to the reads alignment.
646 * <p>
647 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
648 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
649 * and the base calls sequence are assumed.
650 *
651 * @param name the name of the new sequence
652 * @param pherogramURI the URI where the associated pherogram file is located
653 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
654 * be added, {@code false} otherwise.
655 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
656 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
657 * @return the sequence ID of the added read
658 * @throws IOException if an error occurred when trying to read the pherogram file
659 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
660 */
661 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
662 Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
663 throws IOException, UnsupportedChromatogramFormatException {
664
665 AlignmentModel provider = getReadsArea().getAlignmentModel();
666 PherogramProvider pherogramProvider = null;
667 if (pherogramURI != null) {
668 pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
669 if (reverseComplemented) {
670 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
671 }
672 }
673
674 // Create sequence:
675 provider.addSequence(name);
676 int id = provider.sequenceIDByName(name);
677
678 // Set edited sequence:
679 Collection<Object> tokens = null; // First save tokens in a collection to avoid GUI updated for each token.
680 if (editedSequence != null) {
681 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
682 }
683 else if (pherogramProvider != null) { // Copy base call sequence into alignment:
684 tokens = new ArrayList<Object>();
685 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
686 tokens.add(provider.getTokenSet().tokenByRepresentation(
687 Character.toString(pherogramProvider.getBaseCall(i))));
688 }
689 setDirty();
690 }
691
692 if (tokens != null) { // If either an edited sequence or a pherogram URI was provided.
693 provider.insertTokensAt(id, 0, tokens);
694
695 if (pherogramProvider != null) {
696 // Create pherogram area:
697 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
698 new PherogramAreaModel(pherogramProvider));
699
700 // Set position properties and shifts:
701 PherogramAreaModel model = pherogramArea.getModel();
702 if ((firstSeqPos != null) && (leftCutPos != null)) {
703 model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
704 }
705 if (rightCutPos != null) {
706 model.setRightCutPosition(rightCutPos);
707 }
708 if ((shifts != null) && (shifts.length > 0)) {
709 for (int i = 0; i < shifts.length; i++) {
710 model.addShiftChange(shifts[i].position, shifts[i].shift);
711 }
712 setDirty();
713 }
714
715 // Add pherogram area to GUI:
716 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
717 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
718 }
719 }
720 return id;
721 }
722 }