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