--- /dev/null
- singleReadAlignment.getFirstSeqPosition(),
- singleReadAlignment.getLeftCutPosition(),
- singleReadAlignment.getRightCutPosition(),
+// $Id$
+/**
+* Copyright (C) 2014 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.taxeditor.editor.molecular;
+
+
+import info.bioinfweb.jphyloio.events.TokenSetType;
+import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
+import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
+import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
+import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
+import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
+import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
+import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
+import info.bioinfweb.libralign.editsettings.EditSettingsListener;
+import info.bioinfweb.libralign.model.AlignmentModel;
+import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
+import info.bioinfweb.libralign.model.AlignmentModelUtils;
+import info.bioinfweb.libralign.model.adapters.StringAdapter;
+import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
+import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
+import info.bioinfweb.libralign.model.events.TokenChangeEvent;
+import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
+import info.bioinfweb.libralign.model.tokenset.BioJavaTokenSet;
+import info.bioinfweb.libralign.model.tokenset.TokenSet;
+import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
+import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
+import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
+import info.bioinfweb.libralign.pherogram.model.ShiftChange;
+import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
+import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
+import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.biojava.bio.chromatogram.ChromatogramFactory;
+import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
+import org.biojava3.core.sequence.compound.DNACompoundSet;
+import org.biojava3.core.sequence.compound.NucleotideCompound;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandService;
+import org.eclipse.ui.part.EditorPart;
+
+import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
+import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
+import eu.etaxonomy.cdm.model.media.MediaUtils;
+import eu.etaxonomy.cdm.model.molecular.Sequence;
+import eu.etaxonomy.cdm.model.molecular.SequenceString;
+import eu.etaxonomy.cdm.model.molecular.SingleRead;
+import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
+import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleInsertOverwriteHandler;
+import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleLeftRightInsertionHandler;
+import eu.etaxonomy.taxeditor.store.CdmStore;
+
+
+
+/**
+ * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
+ * a consensus sequence.
+ * <p>
+ * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
+ *
+ * @author Ben Stöver
+ * @author pplitzner
+ * @date 04.08.2014
+ */
+public class AlignmentEditor extends EditorPart {
+ public static final String ID = "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
+
+ public static final int READS_AREA_INDEX = 1;
+ public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
+ public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
+ public static final int PHEROGRAM_AREA_INDEX = 0;
+ public static final int CONSENSUS_DATA_AREA_INDEX = 0;
+ public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
+ public static final String CONSENSUS_NAME = "Consensus";
+
+
+ private final ConversationHolder conversationHolder;
+
+ private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
+ @Override
+ public <T> void afterTokenChange(TokenChangeEvent<T> e) {
+ setDirty();
+ }
+
+ @Override
+ public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
+ setDirty();
+ }
+
+ @Override
+ public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
+ setDirty();
+ }
+
+ @Override
+ public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
+ AlignmentModel<U> newProvider) { // Not expected.
+
+ setDirty();
+ }
+ };
+
+ private MultipleAlignmentsContainer alignmentsContainer = null;
+ private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
+ private boolean dirty = false;
+
+
+ public AlignmentEditor() {
+ super();
+ //conversationHolder = CdmStore.createConversation();
+ conversationHolder = null;
+ }
+
+
+ private void refreshToolbarElement(String id) {
+ ICommandService commandService =
+ (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
+ if (commandService != null) {
+ commandService.refreshElements(id, Collections.EMPTY_MAP);
+ }
+ }
+
+
+ private void registerEditSettingListener(MultipleAlignmentsContainer container) {
+ container.getEditSettings().addListener(new EditSettingsListener() {
+ @Override
+ public void workingModeChanged(EditSettingsChangeEvent e) {} // Currently nothing to do
+
+ @Override
+ public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
+ updateStatusBar();
+ refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
+ }
+
+ @Override
+ public void insertChanged(EditSettingsChangeEvent e) {
+ updateStatusBar();
+ refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
+ }
+ });
+ }
+
+
+ private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
+ AlignmentArea result = new AlignmentArea(container);
+ result.setAllowVerticalScrolling(false);
+ result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
+ return result;
+ }
+
+
+ private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
+ AlignmentArea result = new AlignmentArea(container);
+ result.setAllowVerticalScrolling(allowVerticalScrolling);
+
+ TokenSet<NucleotideCompound> tokenSet = new BioJavaTokenSet<NucleotideCompound>(
+ TokenSetType.DNA, new DNACompoundSet(), true); //TODO Should NUCLEOTIDE be used instead?
+ AlignmentModel<NucleotideCompound> provider = new PackedAlignmentModel<NucleotideCompound>(tokenSet);
+ result.setAlignmentModel(provider, false);
+ provider.getChangeListeners().add(DIRTY_LISTENER);
+ result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
+
+ return result;
+ }
+
+
+ private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
+ AlignmentArea labeledArea) {
+
+ AlignmentArea result = new AlignmentArea(container);
+ result.setAllowVerticalScrolling(false);
+ result.getDataAreas().getBottomAreas().add(
+ new ConsensusSequenceArea(result.getContentArea(), labeledArea));
+ return result;
+ }
+
+
+ private MultipleAlignmentsContainer getAlignmentsContainer() {
+ if (alignmentsContainer == null) {
+ alignmentsContainer = new MultipleAlignmentsContainer();
+
+ AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
+ AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
+ list.add(createIndexArea(alignmentsContainer, readsArea));
+ list.add(readsArea); // Make sure READS_AREA_INDEX is correct.
+ list.add(createEditableAlignmentArea(alignmentsContainer, false)); // Make sure COMSENSUS_AREA_INDEX is correct.
+ list.add(createConsensusHintArea(alignmentsContainer, readsArea));
+
+ registerEditSettingListener(alignmentsContainer);
+ }
+ return alignmentsContainer;
+ }
+
+
+ public AlignmentArea getReadsArea() {
+ return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
+ }
+
+
+ private AlignmentArea getEditableConsensusArea() {
+ return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
+ }
+
+
+ public PherogramArea getPherogramArea(int sequenceID) {
+ return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(
+ PHEROGRAM_AREA_INDEX);
+ }
+
+
+ private ConsensusSequenceArea getConsensusHintDataArea() {
+ return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
+ get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
+ get(CONSENSUS_DATA_AREA_INDEX);
+ }
+
+
+ private void createTestContents() {
+ // Just for testing:
+ try {
+ 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);
+ //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);
+ 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);
+
+ // Add test consensus sequence:
+ AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
+ int id = consensusModel.addSequence(CONSENSUS_NAME);
+ Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
+ tokens.add(consensusModel.getTokenSet().tokenByKeyChar('A'));
+ tokens.add(consensusModel.getTokenSet().tokenByKeyChar('C'));
+ tokens.add(consensusModel.getTokenSet().tokenByKeyChar('G'));
+ tokens.add(consensusModel.getTokenSet().tokenByKeyChar('T'));
+ consensusModel.insertTokensAt(id, 0, tokens);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private void readCDMData(Sequence sequenceNode) {
+ //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
+
+ // Add reads:
+ for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
+ try {
+ SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
+ 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?
+ MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri(),
+ singleReadAlignment.isReverseComplement(),
+ singleReadAlignment.getEditedSequence(),
++ singleReadAlignment.getFirstSeqPosition()!=null?singleReadAlignment.getFirstSeqPosition():0,
++ singleReadAlignment.getLeftCutPosition()!=null?singleReadAlignment.getLeftCutPosition():0,
++ singleReadAlignment.getRightCutPosition()!=null?singleReadAlignment.getRightCutPosition():0,
+ singleReadAlignment.getShifts());
+ cdmMap.put(id, singleReadAlignment);
+ }
+ catch (IOException e) {
+ e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
+ }
+ catch (UnsupportedChromatogramFormatException e) {
+ e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
+ }
+ }
+
+ // Set consensus sequence:
+ AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
+ int id = consensusProvider.addSequence(CONSENSUS_NAME);
+ consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
+ sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
+ //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ public void createPartControl(Composite parent) {
+ getAlignmentsContainer().createSWTWidget(parent, SWT.NONE);
+ updateStatusBar();
+
+ if (getEditorInput() instanceof AlignmentEditorInput) {
+ if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
+ Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
+ //re-load into the current session if it is already persisted in the DB
+ if(sequenceNode!=null && sequenceNode.getId()!=0){
+ sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
+ }
+ readCDMData(sequenceNode);
+ }
+ else {
+ createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
+ }
+ }
+ else {
+ throw new IllegalArgumentException("The editor input must have the type " +
+ AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
+ }
+ }
+
+
+ private void updateStatusBar() {
+ IActionBars bars = getEditorSite().getActionBars();
+ bars.getStatusLineManager().setMessage("Edit mode: " +
+ (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
+ "Insertion in pherogram: " +
+ (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
+ }
+
+
+ private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
+ SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[model.getShiftChangeCount()];
+ Iterator<ShiftChange> iterator = model.shiftChangeIterator();
+ int pos = 0;
+ while (iterator.hasNext()) {
+ ShiftChange shiftChange = iterator.next();
+ result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
+ }
+ return result;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ @Override
+ public void doSave(IProgressMonitor monitor) {
+ if (getEditorInput() instanceof AlignmentEditorInput) {
+ String taskName = "Saving alignment";
+ monitor.beginTask(taskName, 3);
+
+ Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
+ StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
+
+ // Write consensus sequence:
+ SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
+ String newConsensusSequence = stringProvider.getSequence(
+ getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
+ if (consensusSequenceObj == null) {
+ sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
+ }
+ else {
+ consensusSequenceObj.setString(newConsensusSequence);
+ }
+
+ // Write single reads:
+ stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
+ sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
+ Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
+ while (iterator.hasNext()) {
+ int id = iterator.next();
+ SingleReadAlignment singleRead = cdmMap.get(id);
+ if (singleRead == null) {
+ //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?
+ //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
+ }
+
+ singleRead.setEditedSequence(stringProvider.getSequence(id));
+
+ PherogramAreaModel model = getPherogramArea(id).getModel();
+ singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
+ singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
+ singleRead.setFirstSeqPosition(model.getFirstSeqPos());
+ singleRead.setLeftCutPosition(model.getLeftCutPosition());
+ singleRead.setRightCutPosition(model.getRightCutPosition());
+ }
+
+ if (!conversationHolder.isBound()) {
+ conversationHolder.bind();
+ }
+ monitor.worked(1);
+
+ // Commit the conversation and start a new transaction immediately:
+ conversationHolder.commit(true);
+ monitor.worked(1);
+
+ dirty = false;
+ monitor.worked(1);
+ monitor.done();
+ firePropertyChange(PROP_DIRTY);
+ }
+ else {
+ //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.EditorPart#doSaveAs()
+ */
+ @Override
+ public void doSaveAs() {}
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
+ */
+ @Override
+ public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+ setSite(site);
+ setInput(input);
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.EditorPart#isDirty()
+ */
+ @Override
+ public boolean isDirty() {
+ return dirty;
+ }
+
+
+ private void setDirty() {
+ dirty = true;
+ firePropertyChange(IEditorPart.PROP_DIRTY);
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
+ */
+ @Override
+ public boolean isSaveAsAllowed() {
+ return false; // "Save as" not allowed.
+ }
+
+
+ @Override
+ public void setFocus() {
+ if(conversationHolder!=null){
+ conversationHolder.bind();
+ }
+ }
+
+
+ public boolean isInsertMode() {
+ return getAlignmentsContainer().getEditSettings().isInsert();
+ }
+
+
+ public boolean isInsertLeftInPherogram() {
+ return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
+ }
+
+
+ public void toggleLeftRightInsertionInPherogram() {
+ getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
+ }
+
+
+ public void toggleInsertOverwrite() {
+ getAlignmentsContainer().getEditSettings().toggleInsert();
+ }
+
+
+ private String cutPherogram(boolean left) {
+ SelectionModel selection = getReadsArea().getSelection();
+ if (selection.getCursorHeight() != 1) {
+ return "Cutting pherograms is only possible if exactly one row is selected.";
+ }
+ else {
+ PherogramArea pherogramArea =
+ getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
+ if (pherogramArea == null) {
+ return "There is no pherogram attached to the current sequence.";
+ }
+ else {
+ if (left) {
+ if (pherogramArea.setLeftCutPositionBySelection()) {
+ return null;
+ }
+ else {
+ return "The left end of the selection lies outside the pherogram attached to this sequence.";
+ }
+ }
+ else {
+ if (pherogramArea.setRightCutPositionBySelection()) {
+ return null;
+ }
+ else {
+ return "The right end of the selection lies outside the pherogram attached to this sequence.";
+ }
+ }
+ }
+ }
+ }
+
+
+ public String cutPherogramLeft() {
+ return cutPherogram(true);
+ }
+
+
+ public String cutPherogramRight() {
+ return cutPherogram(false);
+ }
+
+
+ public void reverseComplementSelectedSequences() {
+ SelectionModel selection = getReadsArea().getSelection();
+ AlignmentModel<?> model = getReadsArea().getAlignmentModel();
+ for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
+ int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
+ PherogramArea area = getPherogramArea(sequenceID);
+ PherogramAreaModel pherogramAlignmentModel = area.getModel();
+ AlignmentModelUtils.reverseComplement(model, sequenceID,
+ pherogramAlignmentModel.editableIndexByBaseCallIndex(
+ pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
+ pherogramAlignmentModel.editableIndexByBaseCallIndex(
+ pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
+ pherogramAlignmentModel.reverseComplement();
+ }
+ }
+
+
+ /**
+ * Recreates the whole consensus sequence from all single read sequences. The previous consensus
+ * sequence is overwritte.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> void createConsensusSequence() {
+ ConsensusSequenceArea area = getConsensusHintDataArea();
+ AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
+ int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
+ int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
+
+ Collection<T> tokens = new ArrayList<T>(length);
+ for (int column = 0; column < length; column++) {
+ tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
+ }
+
+ model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
+ model.insertTokensAt(sequenceID, 0, tokens);
+ }
+
+
+ /**
+ * Updates the current consensus sequence by replacing gaps by the according consensus tokens
+ * calculated from the single read sequences and extends the consensus sequence if necessary.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> void updateConsensusSequence() {
+ ConsensusSequenceArea area = getConsensusHintDataArea();
+ AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
+ TokenSet<T> tokenSet = model.getTokenSet();
+ int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
+ int currentConsensusLength = model.getSequenceLength(sequenceID);
+ int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
+
+ // Replace gaps by new information:
+ for (int column = 0; column < currentConsensusLength; column++) {
+ if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
+ T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
+ if (!tokenSet.isGapToken(newToken)) {
+ model.setTokenAt(sequenceID, column, newToken);
+ }
+ }
+ }
+
+ // Append additional tokens:
+ if (overallLength > currentConsensusLength) {
+ Collection<T> tokens = new ArrayList<T>(overallLength);
+ for (int column = currentConsensusLength; column < overallLength; column++) {
+ tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
+ }
+ model.appendTokens(sequenceID, tokens);
+ }
+ }
+
+
+ public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
+ PherogramProvider result;
+ InputStream stream = uri.toURL().openStream();
+ try {
+ result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
+ }
+ finally {
+ stream.close();
+ }
+ return result;
+ }
+
+
+ private String newReadName() {
+ int index = 1;
+ while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
+ != AlignmentModel.NO_SEQUENCE_FOUND) {
+
+ index++;
+ }
+ return DEFAULT_READ_NAME_PREFIX + index;
+ }
+
+
+ public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
+ addRead(newReadName(), pherogramURI, reverseComplemented, null, 0, 0, 0, null); // Position values will be ignored, if shifts == null.
+ }
+
+
+ /**
+ * Adds a new sequence with attached phergram data area to the reads alignment.
+ * <p>
+ * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
+ * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
+ * and the base calls sequence are assumed.
+ *
+ * @param name the name of the new sequence
+ * @param pherogramURI the URI where the associated pherogram file is located
+ * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
+ * be added, {@code false} otherwise.
+ * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
+ * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
+ * @return the sequence ID of the added read
+ * @throws IOException if an error occurred when trying to read the pherogram file
+ * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
+ */
+ public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
+ int firstSeqPos, int leftCutPos, int rightCutPos, SingleReadAlignment.Shift[] shifts)
+ throws IOException, UnsupportedChromatogramFormatException {
+
+ AlignmentModel provider = getReadsArea().getAlignmentModel();
+ PherogramProvider pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
+ if (reverseComplemented) {
+ pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
+ }
+
+ // Create sequence:
+ provider.addSequence(name);
+ int id = provider.sequenceIDByName(name);
+
+ // Set edited sequence:
+ Collection<Object> tokens; // First save tokens in a collection to avoid GUI updated for each token.
+ if (editedSequence != null) {
+ tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
+ }
+ else { // Copy base call sequence into alignment:
+ tokens = new ArrayList<Object>();
+ for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
+ tokens.add(provider.getTokenSet().tokenByKeyChar(
+ pherogramProvider.getBaseCall(i).getUpperedBase().charAt(0)));
+ }
+ setDirty();
+ }
+ provider.insertTokensAt(id, 0, tokens);
+
+ // Create pherogram area:
+ PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
+ new PherogramAreaModel(pherogramProvider));
+
+ // Set position properties and shifts:
+ if ((shifts != null) && (shifts.length > 0)) {
+ PherogramAreaModel model = pherogramArea.getModel();
+ model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
+ model.setRightCutPosition(rightCutPos);
+
+ for (int i = 0; i < shifts.length; i++) {
+ model.addShiftChange(shifts[i].position, shifts[i].shift);
+ }
+ setDirty();
+ }
+
+ // Add pherogram area to GUI:
+ pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
+ getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
+
+ // Save source URI:
+ return id;
+ }
+}