3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.taxeditor
.editor
.molecular
;
13 import info
.bioinfweb
.libralign
.alignmentarea
.AlignmentArea
;
14 import info
.bioinfweb
.libralign
.alignmentarea
.selection
.SelectionModel
;
15 import info
.bioinfweb
.libralign
.dataarea
.implementations
.ConsensusSequenceArea
;
16 import info
.bioinfweb
.libralign
.dataarea
.implementations
.SequenceIndexArea
;
17 import info
.bioinfweb
.libralign
.dataarea
.implementations
.pherogram
.PherogramAlignmentModel
;
18 import info
.bioinfweb
.libralign
.dataarea
.implementations
.pherogram
.PherogramArea
;
19 import info
.bioinfweb
.libralign
.dataarea
.implementations
.pherogram
.ShiftChange
;
20 import info
.bioinfweb
.libralign
.editsettings
.EditSettingsChangeEvent
;
21 import info
.bioinfweb
.libralign
.editsettings
.EditSettingsListener
;
22 import info
.bioinfweb
.libralign
.multiplealignments
.AlignmentAreaList
;
23 import info
.bioinfweb
.libralign
.multiplealignments
.MultipleAlignmentsContainer
;
24 import info
.bioinfweb
.libralign
.pherogram
.provider
.BioJavaPherogramProvider
;
25 import info
.bioinfweb
.libralign
.pherogram
.provider
.PherogramProvider
;
26 import info
.bioinfweb
.libralign
.pherogram
.provider
.ReverseComplementPherogramProvider
;
27 import info
.bioinfweb
.libralign
.sequenceprovider
.SequenceDataChangeListener
;
28 import info
.bioinfweb
.libralign
.sequenceprovider
.SequenceDataProvider
;
29 import info
.bioinfweb
.libralign
.sequenceprovider
.SequenceUtils
;
30 import info
.bioinfweb
.libralign
.sequenceprovider
.adapters
.StringAdapter
;
31 import info
.bioinfweb
.libralign
.sequenceprovider
.events
.SequenceChangeEvent
;
32 import info
.bioinfweb
.libralign
.sequenceprovider
.events
.SequenceRenamedEvent
;
33 import info
.bioinfweb
.libralign
.sequenceprovider
.events
.TokenChangeEvent
;
34 import info
.bioinfweb
.libralign
.sequenceprovider
.implementations
.PackedSequenceDataProvider
;
35 import info
.bioinfweb
.libralign
.sequenceprovider
.tokenset
.BioJavaTokenSet
;
36 import info
.bioinfweb
.libralign
.sequenceprovider
.tokenset
.TokenSet
;
39 import java
.io
.IOException
;
40 import java
.io
.InputStream
;
42 import java
.util
.ArrayList
;
43 import java
.util
.Collection
;
44 import java
.util
.Collections
;
45 import java
.util
.Iterator
;
47 import java
.util
.TreeMap
;
49 import org
.biojava
.bio
.chromatogram
.ChromatogramFactory
;
50 import org
.biojava
.bio
.chromatogram
.UnsupportedChromatogramFormatException
;
51 import org
.biojava3
.core
.sequence
.compound
.DNACompoundSet
;
52 import org
.biojava3
.core
.sequence
.compound
.NucleotideCompound
;
53 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
54 import org
.eclipse
.swt
.SWT
;
55 import org
.eclipse
.swt
.widgets
.Composite
;
56 import org
.eclipse
.ui
.IActionBars
;
57 import org
.eclipse
.ui
.IEditorInput
;
58 import org
.eclipse
.ui
.IEditorPart
;
59 import org
.eclipse
.ui
.IEditorSite
;
60 import org
.eclipse
.ui
.PartInitException
;
61 import org
.eclipse
.ui
.PlatformUI
;
62 import org
.eclipse
.ui
.commands
.ICommandService
;
63 import org
.eclipse
.ui
.part
.EditorPart
;
65 import eu
.etaxonomy
.cdm
.api
.conversation
.ConversationHolder
;
66 import eu
.etaxonomy
.cdm
.api
.service
.molecular
.ISequenceService
;
67 import eu
.etaxonomy
.cdm
.model
.media
.MediaUtils
;
68 import eu
.etaxonomy
.cdm
.model
.molecular
.Sequence
;
69 import eu
.etaxonomy
.cdm
.model
.molecular
.SequenceString
;
70 import eu
.etaxonomy
.cdm
.model
.molecular
.SingleRead
;
71 import eu
.etaxonomy
.cdm
.model
.molecular
.SingleReadAlignment
;
72 import eu
.etaxonomy
.taxeditor
.editor
.handler
.alignmenteditor
.ToggleInsertOverwriteHandler
;
73 import eu
.etaxonomy
.taxeditor
.editor
.handler
.alignmenteditor
.ToggleLeftRightInsertionHandler
;
74 import eu
.etaxonomy
.taxeditor
.store
.CdmStore
;
79 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
80 * a consensus sequence.
82 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
88 public class AlignmentEditor
extends EditorPart
{
89 public static final String ID
= "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
91 public static final int READS_AREA_INDEX
= 1;
92 public static final int CONSENSUS_AREA_INDEX
= READS_AREA_INDEX
+ 1;
93 public static final int PHEROGRAM_AREA_INDEX
= 0;
94 public static final String DEFAULT_READ_NAME_PREFIX
= "Read ";
95 public static final String CONSENSUS_NAME
= "Consensus";
98 private final ConversationHolder conversationHolder
;
100 private final SequenceDataChangeListener DIRTY_LISTENER
= new SequenceDataChangeListener() {
102 public <T
> void afterTokenChange(TokenChangeEvent
<T
> e
) {
107 public <T
> void afterSequenceRenamed(SequenceRenamedEvent
<T
> e
) {
112 public <T
> void afterSequenceChange(SequenceChangeEvent
<T
> e
) {
117 public <T
, U
> void afterProviderChanged(SequenceDataProvider
<T
> oldProvider
,
118 SequenceDataProvider
<U
> newProvider
) { // Not expected.
124 private MultipleAlignmentsContainer alignmentsContainer
= null;
125 private final Map
<Integer
, SingleReadAlignment
> cdmMap
= new TreeMap
<Integer
, SingleReadAlignment
>(); //TODO Move this to ContigSequenceDataProvider
126 private boolean dirty
= false;
129 public AlignmentEditor() {
130 conversationHolder
= CdmStore
.createConversation();
134 private void refreshToolbarElement(String id
) {
135 ICommandService commandService
=
136 (ICommandService
)PlatformUI
.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService
.class);
137 if (commandService
!= null) {
138 commandService
.refreshElements(id
, Collections
.EMPTY_MAP
);
143 private void registerEditSettingListener(MultipleAlignmentsContainer container
) {
144 container
.getEditSettings().addListener(new EditSettingsListener() {
146 public void workingModeChanged(EditSettingsChangeEvent e
) {} // Currently nothing to do
149 public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e
) {
151 refreshToolbarElement(ToggleLeftRightInsertionHandler
.COMMAND_ID
);
155 public void insertChanged(EditSettingsChangeEvent e
) {
157 refreshToolbarElement(ToggleInsertOverwriteHandler
.COMMAND_ID
);
163 private AlignmentArea
createIndexArea(MultipleAlignmentsContainer container
) {
164 AlignmentArea result
= new AlignmentArea(container
);
165 result
.setAllowVerticalScrolling(false);
166 result
.getDataAreas().getTopAreas().add(new SequenceIndexArea(result
.getContentArea()));
171 private AlignmentArea
createEditableAlignmentArea(MultipleAlignmentsContainer container
, boolean allowVerticalScrolling
) {
172 AlignmentArea result
= new AlignmentArea(container
);
173 result
.setAllowVerticalScrolling(allowVerticalScrolling
);
175 TokenSet
<NucleotideCompound
> tokenSet
= new BioJavaTokenSet
<NucleotideCompound
>(new DNACompoundSet(), true);
176 SequenceDataProvider
<NucleotideCompound
> provider
= new PackedSequenceDataProvider
<NucleotideCompound
>(tokenSet
);
177 result
.setSequenceProvider(provider
, false);
178 provider
.getChangeListeners().add(DIRTY_LISTENER
);
184 private AlignmentArea
createConsensusHintArea(MultipleAlignmentsContainer container
,
185 SequenceDataProvider
<?
> sequenceProvider
) {
187 AlignmentArea result
= new AlignmentArea(container
);
188 result
.setAllowVerticalScrolling(false);
189 result
.getDataAreas().getBottomAreas().add(
190 new ConsensusSequenceArea(result
.getContentArea(), sequenceProvider
));
195 private MultipleAlignmentsContainer
getAlignmentsContainer() {
196 if (alignmentsContainer
== null) {
197 alignmentsContainer
= new MultipleAlignmentsContainer();
199 AlignmentAreaList list
= alignmentsContainer
.getAlignmentAreas();
200 list
.add(createIndexArea(alignmentsContainer
));
201 AlignmentArea readsArea
= createEditableAlignmentArea(alignmentsContainer
, true);
202 list
.add(readsArea
); // Make sure READS_AREA_INDEX is correct.
203 list
.add(createEditableAlignmentArea(alignmentsContainer
, false)); // Make sure COMSENSUS_AREA_INDEX is correct.
204 list
.add(createConsensusHintArea(alignmentsContainer
,
205 readsArea
.getSequenceProvider()));
207 registerEditSettingListener(alignmentsContainer
);
209 return alignmentsContainer
;
213 private AlignmentArea
getReadsArea() {
214 return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX
);
218 private AlignmentArea
getConsensusArea() {
219 return getAlignmentsContainer().getAlignmentAreas().get(CONSENSUS_AREA_INDEX
);
223 private PherogramArea
getPherogramArea(int sequenceID
) {
224 return (PherogramArea
)getReadsArea().getDataAreas().getSequenceAreas(sequenceID
).get(PHEROGRAM_AREA_INDEX
);
228 private void createTestContents() {
231 addRead(new File("D:/Users/BenStoever/Documents/Studium/Projekte/Promotion/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR430_JR-P01.ab1").toURI(), false);
232 addRead(new File("D:/Users/BenStoever/Documents/Studium/Projekte/Promotion/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR444_JR-P05.ab1").toURI(), false);
234 // Add test consensus sequence:
235 SequenceDataProvider consensusProvider
= getConsensusArea().getSequenceProvider();
236 int id
= consensusProvider
.addSequence(CONSENSUS_NAME
);
237 Collection
<Object
> tokens
= new ArrayList
<Object
>(); // First save tokens in a collection to avoid GUI updated for each token.
238 tokens
.add(consensusProvider
.getTokenSet().tokenByKeyChar('A'));
239 tokens
.add(consensusProvider
.getTokenSet().tokenByKeyChar('C'));
240 tokens
.add(consensusProvider
.getTokenSet().tokenByKeyChar('G'));
241 tokens
.add(consensusProvider
.getTokenSet().tokenByKeyChar('T'));
242 consensusProvider
.insertTokensAt(id
, 0, tokens
);
244 catch (Exception e
) {
245 throw new RuntimeException(e
);
250 private void readCDMData(Sequence sequenceNode
) {
251 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
254 for (SingleReadAlignment singleReadAlignment
: sequenceNode
.getSingleReadAlignments()) {
256 SingleRead pherogramInfo
= singleReadAlignment
.getSingleRead();
257 int id
= addRead(pherogramInfo
.getPrimer().getLabel(), //TODO Should the sequence name contain other/additional/alternative data? Can the same string as in the derivative tree be used here?
258 MediaUtils
.getFirstMediaRepresentationPart(pherogramInfo
.getPherogram()).getUri(),
259 singleReadAlignment
.isReverseComplement(),
260 singleReadAlignment
.getEditedSequence(),
261 singleReadAlignment
.getShifts());
262 cdmMap
.put(id
, singleReadAlignment
);
264 catch (IOException e
) {
265 e
.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
267 catch (UnsupportedChromatogramFormatException e
) {
268 e
.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
272 // Set consensus sequence:
273 SequenceDataProvider consensusProvider
= getConsensusArea().getSequenceProvider();
274 int id
= consensusProvider
.addSequence(CONSENSUS_NAME
);
275 consensusProvider
.insertTokensAt(id
, 0, SequenceUtils
.stringToTokenList(
276 sequenceNode
.getConsensusSequence().getString(), consensusProvider
.getTokenSet()));
277 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
282 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
285 public void createPartControl(Composite parent
) {
286 getAlignmentsContainer().createSWTWidget(parent
, SWT
.NONE
);
289 if (getEditorInput() instanceof AlignmentEditorInput
) {
290 if (((AlignmentEditorInput
)getEditorInput()).getSequenceNode() != null) {
291 Sequence sequenceNode
= ((AlignmentEditorInput
)getEditorInput()).getSequenceNode();
292 //re-load into the current session if it is already persisted in the DB
293 if(sequenceNode
!=null && sequenceNode
.getId()!=0){
294 sequenceNode
= CdmStore
.getService(ISequenceService
.class).load(sequenceNode
.getUuid());
296 readCDMData(sequenceNode
);
299 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
303 throw new IllegalArgumentException("The editor input must have the type " +
304 AlignmentEditorInput
.class.getCanonicalName()); //TODO What should be done here?
309 private void updateStatusBar() {
310 IActionBars bars
= getEditorSite().getActionBars();
311 bars
.getStatusLineManager().setMessage("Edit mode: " +
312 (getReadsArea().getEditSettings().isInsert() ?
"Insert" : "Overwrite") + " " +
313 "Insertion in pherogram: " +
314 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ?
"Left" : "Right"));
318 private SingleReadAlignment
.Shift
[] convertToCDMShifts(PherogramAlignmentModel alignmentModel
) {
319 SingleReadAlignment
.Shift
[] result
= new SingleReadAlignment
.Shift
[alignmentModel
.getShiftChangeCount()];
320 Iterator
<ShiftChange
> iterator
= alignmentModel
.shiftChangeIterator();
322 while (iterator
.hasNext()) {
323 ShiftChange shiftChange
= iterator
.next();
324 result
[pos
] = new SingleReadAlignment
.Shift(shiftChange
.getBaseCallIndex(), shiftChange
.getShiftChange());
331 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
334 public void doSave(IProgressMonitor monitor
) {
335 if (getEditorInput() instanceof AlignmentEditorInput
) {
336 String taskName
= "Saving alignment";
337 monitor
.beginTask(taskName
, 3);
339 Sequence sequenceNode
= ((AlignmentEditorInput
)getEditorInput()).getSequenceNode();
340 StringAdapter stringProvider
= new StringAdapter(getConsensusArea().getSequenceProvider(), false); // Throws an exception if a token has more than one character.
342 // Write consensus sequence:
343 SequenceString consensusSequenceObj
= sequenceNode
.getConsensusSequence();
344 String newConsensusSequence
= stringProvider
.getSequence(
345 getConsensusArea().getSequenceProvider().sequenceIDByName(CONSENSUS_NAME
));
346 if (consensusSequenceObj
== null) {
347 sequenceNode
.setConsensusSequence(SequenceString
.NewInstance(newConsensusSequence
));
350 consensusSequenceObj
.setString(newConsensusSequence
);
353 // Write single reads:
354 stringProvider
.setUnderlyingProvider(getReadsArea().getSequenceProvider());
355 sequenceNode
.getSingleReadAlignments().retainAll(cdmMap
.values()); // Remove all reads that are not in the alignment anymore.
356 Iterator
<Integer
> iterator
= getReadsArea().getSequenceProvider().sequenceIDIterator();
357 while (iterator
.hasNext()) {
358 int id
= iterator
.next();
359 SingleReadAlignment singleRead
= cdmMap
.get(id
);
360 if (singleRead
== null) {
361 //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?
362 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
365 singleRead
.setEditedSequence(stringProvider
.getSequence(id
));
366 singleRead
.setReverseComplement(getPherogramArea(id
).getProvider() instanceof ReverseComplementPherogramProvider
); // Works only if ReverseComplementPherogramProvider instances are not nested.
367 singleRead
.setShifts(convertToCDMShifts(getPherogramArea(id
).getAlignmentModel()));
370 if (!conversationHolder
.isBound()) {
371 conversationHolder
.bind();
375 // Commit the conversation and start a new transaction immediately:
376 conversationHolder
.commit(true);
382 firePropertyChange(PROP_DIRTY
);
385 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
391 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
394 public void doSaveAs() {}
398 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
401 public void init(IEditorSite site
, IEditorInput input
) throws PartInitException
{
408 * @see org.eclipse.ui.part.EditorPart#isDirty()
411 public boolean isDirty() {
416 private void setDirty() {
418 firePropertyChange(IEditorPart
.PROP_DIRTY
);
423 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
426 public boolean isSaveAsAllowed() {
427 return false; // "Save as" not allowed.
432 public void setFocus() {
433 if(conversationHolder
!=null){
434 conversationHolder
.bind();
439 public boolean isInsertMode() {
440 return getAlignmentsContainer().getEditSettings().isInsert();
444 public boolean isInsertLeftInPherogram() {
445 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
449 public void toggleLeftRightInsertionInPherogram() {
450 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
454 public void toggleInsertOverwrite() {
455 getAlignmentsContainer().getEditSettings().toggleInsert();
459 public void reverseComplementSelection() {
460 SelectionModel selection
= getReadsArea().getSelection();
461 SequenceDataProvider
<?
> provider
= getReadsArea().getSequenceProvider();
462 for (int row
= selection
.getStartRow(); row
< selection
.getStartRow() + selection
.getCursorHeight(); row
++) {
463 int sequenceID
= getReadsArea().getSequenceOrder().idByIndex(row
);
464 //TODO rc edited sequence
466 if (getPherogramArea(sequenceID
).getProvider() instanceof ReverseComplementPherogramProvider
) {
467 //getPherogramArea(sequenceID).
468 //TODO Allow to set new provider in PherogramArea or create new PherogramArea
469 //TODO Reposition pherogram according to previous position in edited sequence and length
475 public static PherogramProvider
readPherogram(URI uri
) throws IOException
, UnsupportedChromatogramFormatException
{
476 PherogramProvider result
;
477 InputStream stream
= uri
.toURL().openStream();
479 result
= new BioJavaPherogramProvider(ChromatogramFactory
.create(stream
));
488 private String
newReadName() {
490 while (getReadsArea().getSequenceProvider().sequenceIDByName(DEFAULT_READ_NAME_PREFIX
+ index
)
491 != SequenceDataProvider
.NO_SEQUENCE_FOUND
) {
495 return DEFAULT_READ_NAME_PREFIX
+ index
;
499 public void addRead(URI pherogramURI
, boolean reverseComplemented
) throws IOException
, UnsupportedChromatogramFormatException
{
500 addRead(newReadName(), pherogramURI
, reverseComplemented
, null, null);
505 * Adds a new sequence with attached phergram data area to the reads alignment.
507 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
508 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
509 * and the base calls sequence are assumed.
511 * @param name the name of the new sequence
512 * @param pherogramURI the URI where the associated pherogram file is located
513 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
514 * be added, {@code false} otherwise.
515 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
516 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
517 * @return the sequence ID of the added read
518 * @throws IOException if an error occurred when trying to read the pherogram file
519 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
521 public int addRead(String name
, URI pherogramURI
, boolean reverseComplemented
, String editedSequence
,
522 SingleReadAlignment
.Shift
[] shifts
) throws IOException
, UnsupportedChromatogramFormatException
{
524 SequenceDataProvider provider
= getReadsArea().getSequenceProvider();
525 PherogramProvider pherogramProvider
= readPherogram(pherogramURI
); // Must happen before a sequence is added, because it might throw an exception.
526 if (reverseComplemented
) {
527 pherogramProvider
= new ReverseComplementPherogramProvider(pherogramProvider
);
531 provider
.addSequence(name
);
532 int id
= provider
.sequenceIDByName(name
);
534 // Set edited sequence:
535 Collection
<Object
> tokens
; // First save tokens in a collection to avoid GUI updated for each token.
536 if (editedSequence
!= null) {
537 tokens
= SequenceUtils
.stringToTokenList(editedSequence
, provider
.getTokenSet());
539 else { // Copy base call sequence into alignment:
540 tokens
= new ArrayList
<Object
>();
541 for (int i
= 0; i
< pherogramProvider
.getSequenceLength(); i
++) {
542 tokens
.add(provider
.getTokenSet().tokenByKeyChar(
543 pherogramProvider
.getBaseCall(i
).getUpperedBase().charAt(0)));
547 provider
.insertTokensAt(id
, 0, tokens
);
549 // Create pherogram area:
550 PherogramArea pherogramArea
= new PherogramArea(getReadsArea().getContentArea(), pherogramProvider
);
553 if ((shifts
!= null) && (shifts
.length
> 0)) {
554 PherogramAlignmentModel alignmentModel
= pherogramArea
.getAlignmentModel();
555 for (int i
= 0; i
< shifts
.length
; i
++) {
556 alignmentModel
.addShiftChange(shifts
[i
].position
, shifts
[i
].shift
);
561 // Add pherogram area to GUI:
562 pherogramArea
.addMouseListener(new PherogramMouseListener(pherogramURI
));
563 getReadsArea().getDataAreas().getSequenceAreas(id
).add(pherogramArea
);