1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "MultipleAlignmentObject.h"
23 
24 #include <U2Core/DbiConnection.h>
25 #include <U2Core/MSAUtils.h>
26 #include <U2Core/McaDbiUtils.h>
27 #include <U2Core/MsaDbiUtils.h>
28 #include <U2Core/U2AlphabetUtils.h>
29 #include <U2Core/U2ObjectDbi.h>
30 #include <U2Core/U2OpStatusUtils.h>
31 #include <U2Core/U2SafePoints.h>
32 
33 namespace U2 {
34 
MaSavedState()35 MaSavedState::MaSavedState()
36     : lastState(nullptr) {
37 }
38 
~MaSavedState()39 MaSavedState::~MaSavedState() {
40     delete lastState;
41 }
42 
hasState() const43 bool MaSavedState::hasState() const {
44     return nullptr != lastState;
45 }
46 
takeState()47 const MultipleAlignment MaSavedState::takeState() {
48     const MultipleAlignment state = *lastState;
49     delete lastState;
50     lastState = nullptr;
51     return state;
52 }
53 
setState(const MultipleAlignment & ma)54 void MaSavedState::setState(const MultipleAlignment &ma) {
55     if (nullptr != lastState) {
56         delete lastState;
57     }
58     lastState = new MultipleAlignment(ma->getCopy());
59 }
60 
MultipleAlignmentObject(const QString & gobjectType,const QString & name,const U2EntityRef & maRef,const QVariantMap & hintsMap,const MultipleAlignment & alignment)61 MultipleAlignmentObject::MultipleAlignmentObject(const QString &gobjectType,
62                                                  const QString &name,
63                                                  const U2EntityRef &maRef,
64                                                  const QVariantMap &hintsMap,
65                                                  const MultipleAlignment &alignment)
66     : GObject(gobjectType, name, hintsMap),
67       cachedMa(alignment->getCopy()) {
68     entityRef = maRef;
69     dataLoaded = false;
70 
71     if (!cachedMa->isEmpty()) {
72         dataLoaded = true;
73     }
74 }
75 
~MultipleAlignmentObject()76 MultipleAlignmentObject::~MultipleAlignmentObject() {
77     emit si_invalidateAlignmentObject();
78 }
79 
setTrackMod(U2OpStatus & os,U2TrackModType trackMod)80 void MultipleAlignmentObject::setTrackMod(U2OpStatus &os, U2TrackModType trackMod) {
81     // Prepare the connection
82     DbiConnection con(entityRef.dbiRef, os);
83     CHECK_OP(os, );
84 
85     U2ObjectDbi *objectDbi = con.dbi->getObjectDbi();
86     SAFE_POINT(nullptr != objectDbi, "NULL Object Dbi", );
87 
88     // Set the new status
89     objectDbi->setTrackModType(entityRef.entityId, trackMod, os);
90 }
91 
replaceCharacter(int startPos,int rowIndex,char newChar)92 void MultipleAlignmentObject::replaceCharacter(int startPos, int rowIndex, char newChar) {
93     replaceCharacters({startPos, 1}, rowIndex, newChar);
94 }
95 
replaceCharacters(const U2Region & columnRange,int rowIndex,char newChar)96 void MultipleAlignmentObject::replaceCharacters(const U2Region &columnRange, int rowIndex, char newChar) {
97     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
98 
99     const MultipleAlignment &ma = getMultipleAlignment();
100     SAFE_POINT(U2Region(0, ma->getLength()).contains(columnRange), "Invalid parameters", );
101 
102     const MultipleAlignmentRow &row = ma->getRow(rowIndex);
103     qint64 rowId = row->getRowId();
104     U2OpStatus2Log os;
105 
106     bool isMca = getGObjectType() == GObjectTypes::MULTIPLE_CHROMATOGRAM_ALIGNMENT;
107     if (newChar != U2Msa::GAP_CHAR) {
108         if (isMca) {
109             McaDbiUtils::replaceCharactersInRow(entityRef, rowId, columnRange, newChar, os);
110         } else {
111             MsaDbiUtils::replaceCharactersInRow(entityRef, rowId, columnRange, newChar, os);
112         }
113         CHECK_OP(os, )
114     } else {
115         if (isMca) {
116             McaDbiUtils::removeCharacters(entityRef, {rowId}, columnRange.startPos, columnRange.length, os);
117         } else {
118             MsaDbiUtils::removeRegion(entityRef, {rowId}, columnRange.startPos, columnRange.length, os);
119         }
120         CHECK_OP(os, )
121         MsaDbiUtils::insertGaps(entityRef, {rowId}, columnRange.startPos, columnRange.length, os, true);
122         CHECK_OP(os, )
123     }
124 
125     MaModificationInfo mi;
126     mi.rowContentChanged = true;
127     mi.rowListChanged = false;
128     mi.alignmentLengthChanged = false;
129     mi.modifiedRowIds << rowId;
130 
131     if (newChar != ' ' && !ma->getAlphabet()->contains(newChar)) {
132         const DNAAlphabet *alp = U2AlphabetUtils::findBestAlphabet(QByteArray(1, newChar));
133         const DNAAlphabet *newAlphabet = U2AlphabetUtils::deriveCommonAlphabet(alp, ma->getAlphabet());
134         SAFE_POINT(newAlphabet != nullptr, "Common alphabet is NULL", );
135 
136         if (newAlphabet->getId() != ma->getAlphabet()->getId()) {
137             MaDbiUtils::updateMaAlphabet(entityRef, newAlphabet->getId(), os);
138             mi.alphabetChanged = true;
139             SAFE_POINT_OP(os, );
140         }
141     }
142     updateCachedMultipleAlignment(mi);
143 }
144 
getMultipleAlignment() const145 const MultipleAlignment &MultipleAlignmentObject::getMultipleAlignment() const {
146     ensureDataLoaded();
147     return cachedMa;
148 }
149 
setMultipleAlignment(const MultipleAlignment & newMa,MaModificationInfo mi,const QVariantMap & hints)150 void MultipleAlignmentObject::setMultipleAlignment(const MultipleAlignment &newMa, MaModificationInfo mi, const QVariantMap &hints) {
151     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
152 
153     U2OpStatus2Log os;
154     updateDatabase(os, newMa);
155     SAFE_POINT_OP(os, );
156 
157     mi.hints = hints;
158     updateCachedMultipleAlignment(mi);
159 }
160 
getMultipleAlignmentCopy() const161 const MultipleAlignment MultipleAlignmentObject::getMultipleAlignmentCopy() const {
162     return getMultipleAlignment()->getCopy();
163 }
164 
setGObjectName(const QString & newName)165 void MultipleAlignmentObject::setGObjectName(const QString &newName) {
166     ensureDataLoaded();
167     CHECK(cachedMa->getName() != newName, );
168 
169     if (!isStateLocked()) {
170         U2OpStatus2Log os;
171         MaDbiUtils::renameMa(entityRef, newName, os);
172         CHECK_OP(os, );
173 
174         updateCachedMultipleAlignment();
175     } else {
176         GObject::setGObjectName(newName);
177         cachedMa->setName(newName);
178     }
179 }
180 
getAlphabet() const181 const DNAAlphabet *MultipleAlignmentObject::getAlphabet() const {
182     return getMultipleAlignment()->getAlphabet();
183 }
184 
getLength() const185 qint64 MultipleAlignmentObject::getLength() const {
186     return getMultipleAlignment()->getLength();
187 }
188 
getNumRows() const189 qint64 MultipleAlignmentObject::getNumRows() const {
190     return getMultipleAlignment()->getNumRows();
191 }
192 
getRows() const193 const QList<MultipleAlignmentRow> &MultipleAlignmentObject::getRows() const {
194     return getMultipleAlignment()->getRows();
195 }
196 
getRow(int row) const197 const MultipleAlignmentRow MultipleAlignmentObject::getRow(int row) const {
198     return getMultipleAlignment()->getRow(row);
199 }
200 
getRowPosById(qint64 rowId) const201 int MultipleAlignmentObject::getRowPosById(qint64 rowId) const {
202     return getMultipleAlignment()->getRowsIds().indexOf(rowId);
203 }
204 
getGapModel() const205 U2MsaListGapModel MultipleAlignmentObject::getGapModel() const {
206     return getMultipleAlignment()->getGapModel();
207 }
208 
removeRow(int rowIdx)209 void MultipleAlignmentObject::removeRow(int rowIdx) {
210     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
211 
212     const MultipleAlignment &ma = getMultipleAlignment();
213     SAFE_POINT(rowIdx >= 0 && rowIdx < ma->getNumRows(), "Invalid row index", );
214     qint64 rowId = ma->getRow(rowIdx)->getRowId();
215 
216     U2OpStatus2Log os;
217     removeRowPrivate(os, entityRef, rowId);
218     SAFE_POINT_OP(os, );
219 
220     MaModificationInfo mi;
221     mi.rowContentChanged = false;
222     mi.alignmentLengthChanged = false;
223 
224     QList<qint64> removedRowIds;
225     removedRowIds << rowId;
226 
227     updateCachedMultipleAlignment(mi, removedRowIds);
228 }
229 
removeRowsById(const QList<qint64> & rowIds)230 void MultipleAlignmentObject::removeRowsById(const QList<qint64> &rowIds) {
231     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
232     CHECK(!rowIds.isEmpty(), );
233 
234     QList<qint64> removedRowIds;
235     for (qint64 rowId : qAsConst(rowIds)) {
236         U2OpStatus2Log os;
237         removeRowPrivate(os, entityRef, rowId);
238         if (!os.hasError()) {
239             removedRowIds << rowId;
240         }
241     }
242 
243     MaModificationInfo mi;
244     mi.rowContentChanged = false;
245     mi.alignmentLengthChanged = false;
246     updateCachedMultipleAlignment(mi, removedRowIds);
247 
248     SAFE_POINT(removedRowIds.size() == rowIds.size(), "Failed to remove some rows", );
249 }
250 
removeRows(const QList<int> & rowIndexes)251 void MultipleAlignmentObject::removeRows(const QList<int> &rowIndexes) {
252     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
253     CHECK(!rowIndexes.isEmpty(), );
254 
255     const MultipleAlignment &ma = getMultipleAlignment();
256     QList<qint64> rowIdsToRemove;
257     foreach (int rowIdx, rowIndexes) {
258         SAFE_POINT(rowIdx >= 0 && rowIdx < ma->getNumRows(), "Invalid row index", );
259         qint64 rowId = ma->getRow(rowIdx)->getRowId();
260         rowIdsToRemove << rowId;
261     }
262 
263     removeRowsById(rowIdsToRemove);
264 }
265 
renameRow(int rowIdx,const QString & newName)266 void MultipleAlignmentObject::renameRow(int rowIdx, const QString &newName) {
267     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
268 
269     const MultipleAlignment &ma = getMultipleAlignment();
270     SAFE_POINT(rowIdx >= 0 && rowIdx < ma->getNumRows(), "Invalid row index", );
271     qint64 rowId = ma->getRow(rowIdx)->getRowId();
272 
273     U2OpStatus2Log os;
274     MaDbiUtils::renameRow(entityRef, rowId, newName, os);
275     SAFE_POINT_OP(os, );
276 
277     MaModificationInfo mi;
278     mi.alignmentLengthChanged = false;
279     updateCachedMultipleAlignment(mi);
280 }
281 
isRegionEmpty(int startPos,int startRow,int numChars,int numRows) const282 bool MultipleAlignmentObject::isRegionEmpty(int startPos, int startRow, int numChars, int numRows) const {
283     const MultipleAlignment &ma = getMultipleAlignment();
284     bool isEmpty = true;
285     for (int row = startRow; row < startRow + numRows && isEmpty; ++row) {
286         for (int pos = startPos; pos < startPos + numChars && isEmpty; ++pos) {
287             isEmpty = ma->isGap(row, pos);
288         }
289     }
290     return isEmpty;
291 }
292 
isRegionEmpty(const QList<int> & rowIndexes,int x,int width) const293 bool MultipleAlignmentObject::isRegionEmpty(const QList<int> &rowIndexes, int x, int width) const {
294     const MultipleAlignment &ma = getMultipleAlignment();
295     bool isEmpty = true;
296     for (int i = 0; i < rowIndexes.size() && isEmpty; i++) {
297         int rowIndex = rowIndexes[i];
298         for (int pos = x; pos < x + width && isEmpty; pos++) {
299             isEmpty = ma->isGap(rowIndex, pos);
300         }
301     }
302     return isEmpty;
303 }
304 
moveRowsBlock(int firstRow,int numRows,int shift)305 void MultipleAlignmentObject::moveRowsBlock(int firstRow, int numRows, int shift) {
306     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
307 
308     QList<qint64> rowIds = getMultipleAlignment()->getRowsIds();
309     QList<qint64> rowsToMove;
310 
311     for (int i = 0; i < numRows; ++i) {
312         rowsToMove << rowIds[firstRow + i];
313     }
314 
315     U2OpStatusImpl os;
316     MaDbiUtils::moveRows(entityRef, rowsToMove, shift, os);
317     CHECK_OP(os, );
318 
319     updateCachedMultipleAlignment();
320 }
321 
getRowIds() const322 QList<qint64> MultipleAlignmentObject::getRowIds() const {
323     return getMultipleAlignment()->getRowsIds();
324 }
325 
getRowIdsByRowIndexes(const QList<int> & rowIndexes) const326 QList<qint64> MultipleAlignmentObject::getRowIdsByRowIndexes(const QList<int> &rowIndexes) const {
327     QList<qint64> allRowIds = getRowIds();
328     QList<qint64> resultRowIds;
329     int rowCount = getNumRows();
330     for (int rowIndex : qAsConst(rowIndexes)) {
331         SAFE_POINT(rowIndex >= 0 && rowIndex < rowCount, "Invalid row index: " + QString::number(rowIndex), {});
332         resultRowIds << allRowIds[rowIndex];
333     }
334     return resultRowIds;
335 }
336 
updateRowsOrder(U2OpStatus & os,const QList<qint64> & rowIds)337 void MultipleAlignmentObject::updateRowsOrder(U2OpStatus &os, const QList<qint64> &rowIds) {
338     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
339     if (rowIds == getRowIds()) {
340         return;
341     }
342     MaDbiUtils::updateRowsOrder(entityRef, rowIds, os);
343     CHECK_OP(os, );
344 
345     MaModificationInfo mi;
346     mi.alignmentLengthChanged = false;
347     updateCachedMultipleAlignment(mi);
348 }
349 
changeLength(U2OpStatus & os,qint64 newLength)350 void MultipleAlignmentObject::changeLength(U2OpStatus &os, qint64 newLength) {
351     qint64 length = getLength();
352     CHECK(length != newLength, );
353 
354     MaDbiUtils::updateMaLength(getEntityRef(), newLength, os);
355     CHECK_OP(os, );
356 
357     bool rowContentChangeStatus = false;
358     if (newLength < length) {
359         qint64 numRows = getNumRows();
360         for (int i = 0; i < numRows; i++) {
361             MultipleAlignmentRow row = getRow(i);
362             qint64 rowLengthWithoutTrailing = row->getRowLengthWithoutTrailing();
363             if (rowLengthWithoutTrailing > newLength) {
364                 row->crop(os, 0, newLength);
365                 CHECK_OP(os, );
366                 rowContentChangeStatus = true;
367             }
368         }
369     }
370 
371     MaModificationInfo modificationInfo;
372     modificationInfo.rowContentChanged = rowContentChangeStatus;
373     modificationInfo.rowListChanged = false;
374     updateCachedMultipleAlignment(modificationInfo);
375 }
376 
updateCachedMultipleAlignment(const MaModificationInfo & mi,const QList<qint64> & removedRowIds)377 void MultipleAlignmentObject::updateCachedMultipleAlignment(const MaModificationInfo &mi, const QList<qint64> &removedRowIds) {
378     ensureDataLoaded();
379     emit si_startMaUpdating();
380 
381     MultipleAlignment oldCachedMa = cachedMa->getCopy();
382 
383     // Undo-Redo Framework does not provide detailed change info.
384     // In this case or if we do not know which rows are changed we reload the whole object.
385     bool isUnknownChange = mi.type == MaModificationType_Undo || mi.type == MaModificationType_Redo || (mi.modifiedRowIds.isEmpty() && removedRowIds.isEmpty());
386     U2OpStatus2Log os;
387     if (isUnknownChange) {
388         loadAlignment(os);  // Reload 'cachedMa'.
389         SAFE_POINT_OP(os, );
390     } else {
391         if (mi.alignmentLengthChanged) {
392             qint64 msaLength = MaDbiUtils::getMaLength(entityRef, os);
393             SAFE_POINT_OP(os, );
394             if (msaLength != cachedMa->getLength()) {
395                 cachedMa->setLength(msaLength);
396             }
397         }
398 
399         if (mi.alphabetChanged) {
400             U2AlphabetId alphabet = MaDbiUtils::getMaAlphabet(entityRef, os);
401             SAFE_POINT_OP(os, );
402             if (alphabet.id != cachedMa->getAlphabet()->getId() && !alphabet.id.isEmpty()) {
403                 const DNAAlphabet *newAlphabet = U2AlphabetUtils::getById(alphabet);
404                 cachedMa->setAlphabet(newAlphabet);
405             }
406         }
407 
408         if (!removedRowIds.isEmpty()) {
409             foreach (qint64 rowId, removedRowIds) {
410                 const int rowIndex = cachedMa->getRowIndexByRowId(rowId, os);
411                 SAFE_POINT_OP(os, );
412                 cachedMa->removeRow(rowIndex, os);
413                 SAFE_POINT_OP(os, );
414             }
415         }
416         if (!mi.modifiedRowIds.isEmpty()) {
417             updateCachedRows(os, mi.modifiedRowIds);
418         }
419     }
420 
421     setModified(true);
422     if (!mi.middleState) {
423         emit si_alignmentChanged(oldCachedMa, mi);
424 
425         if (cachedMa->isEmpty() && !oldCachedMa->isEmpty()) {
426             emit si_alignmentBecomesEmpty(true);
427         } else if (!cachedMa->isEmpty() && oldCachedMa->isEmpty()) {
428             emit si_alignmentBecomesEmpty(false);
429         }
430 
431         QString newName = cachedMa->getName();
432         if (oldCachedMa->getName() != newName) {
433             setGObjectNameNotDbi(newName);
434         }
435     }
436     if (!removedRowIds.isEmpty()) {
437         emit si_rowsRemoved(removedRowIds);
438     }
439     if (cachedMa->getAlphabet()->getId() != oldCachedMa->getAlphabet()->getId()) {
440         emit si_alphabetChanged(mi, oldCachedMa->getAlphabet());
441     }
442 }
443 
sortRowsByList(const QStringList & order)444 void MultipleAlignmentObject::sortRowsByList(const QStringList &order) {
445     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
446 
447     MultipleSequenceAlignment ma = getMultipleAlignment()->getCopy();
448     ma->sortRowsByList(order);
449     CHECK(ma->getRowsIds() != cachedMa->getRowsIds(), );
450 
451     U2OpStatusImpl os;
452     MaDbiUtils::updateRowsOrder(entityRef, ma->getRowsIds(), os);
453     SAFE_POINT_OP(os, );
454 
455     MaModificationInfo mi;
456     mi.alignmentLengthChanged = false;
457     mi.rowContentChanged = false;
458     mi.rowListChanged = false;
459     updateCachedMultipleAlignment(mi);
460 }
461 
insertGap(const U2Region & rows,int pos,int nGaps,bool collapseTrailingGaps)462 void MultipleAlignmentObject::insertGap(const U2Region &rows, int pos, int nGaps, bool collapseTrailingGaps) {
463     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
464     const MultipleAlignment &ma = getMultipleAlignment();
465     int startSeq = rows.startPos;
466     int endSeq = startSeq + rows.length;
467 
468     QList<qint64> rowIds;
469     for (int i = startSeq; i < endSeq; ++i) {
470         qint64 rowId = ma->getRow(i)->getRowId();
471         rowIds.append(rowId);
472     }
473     insertGapByRowIdList(rowIds, pos, nGaps, collapseTrailingGaps);
474 }
475 
insertGapByRowIndexList(const QList<int> & rowIndexes,int pos,int nGaps,bool collapseTrailingGaps)476 void MultipleAlignmentObject::insertGapByRowIndexList(const QList<int> &rowIndexes, int pos, int nGaps, bool collapseTrailingGaps) {
477     const MultipleAlignment &ma = getMultipleAlignment();
478     QList<qint64> rowIds;
479     for (int i = 0; i < rowIndexes.size(); i++) {
480         int rowIndex = rowIndexes[i];
481         qint64 rowId = ma->getRow(rowIndex)->getRowId();
482         rowIds.append(rowId);
483     }
484     insertGapByRowIdList(rowIds, pos, nGaps, collapseTrailingGaps);
485 }
486 
insertGapByRowIdList(const QList<qint64> & rowIds,int pos,int nGaps,bool collapseTrailingGaps)487 void MultipleAlignmentObject::insertGapByRowIdList(const QList<qint64> &rowIds, int pos, int nGaps, bool collapseTrailingGaps) {
488     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
489     U2OpStatus2Log os;
490     MsaDbiUtils::insertGaps(entityRef, rowIds, pos, nGaps, os, collapseTrailingGaps);
491     SAFE_POINT_OP(os, );
492 
493     MaModificationInfo mi;
494     mi.rowListChanged = false;
495     mi.modifiedRowIds = rowIds;
496     updateCachedMultipleAlignment(mi);
497 }
498 
499 namespace {
500 
501 template<typename T>
mergeLists(const QList<T> & first,const QList<T> & second)502 inline QList<T> mergeLists(const QList<T> &first, const QList<T> &second) {
503     QList<T> result = first;
504     foreach (const T &item, second) {
505         if (!result.contains(item)) {
506             result.append(item);
507         }
508     }
509     return result;
510 }
511 
getRowsAffectedByDeletion(const MultipleAlignment & ma,const QList<qint64> & removedRowIds)512 QList<qint64> getRowsAffectedByDeletion(const MultipleAlignment &ma, const QList<qint64> &removedRowIds) {
513     QList<qint64> rowIdsAffectedByDeletion;
514     U2OpStatus2Log os;
515     const QList<qint64> maRows = ma->getRowsIds();
516     int previousRemovedRowIndex = -1;
517     foreach (qint64 removedRowId, removedRowIds) {
518         if (-1 != previousRemovedRowIndex) {
519             const int currentRemovedRowIndex = ma->getRowIndexByRowId(removedRowId, os);
520             SAFE_POINT_OP(os, QList<qint64>());
521             SAFE_POINT(currentRemovedRowIndex > previousRemovedRowIndex, "Rows order violation", QList<qint64>());
522             const int countOfUnchangedRowsBetween = currentRemovedRowIndex - previousRemovedRowIndex - 1;
523             if (0 < countOfUnchangedRowsBetween) {
524                 for (int middleRowIndex = previousRemovedRowIndex + 1; middleRowIndex < currentRemovedRowIndex; ++middleRowIndex) {
525                     rowIdsAffectedByDeletion += maRows[middleRowIndex];
526                 }
527             }
528         }
529         previousRemovedRowIndex = ma->getRowIndexByRowId(removedRowId, os);
530         SAFE_POINT_OP(os, QList<qint64>());
531     }
532     const int lastDeletedRowIndex = ma->getRowIndexByRowId(removedRowIds.last(), os);
533     SAFE_POINT_OP(os, QList<qint64>());
534     if (lastDeletedRowIndex < maRows.size() - 1) {  // if the last removed row was not in the bottom of the msa
535         rowIdsAffectedByDeletion += maRows.mid(lastDeletedRowIndex + 1);
536     }
537     return rowIdsAffectedByDeletion;
538 }
539 
540 }  // namespace
541 
removeRegion(const QList<int> & rowIndexes,int x,int width,bool removeEmptyRows)542 void MultipleAlignmentObject::removeRegion(const QList<int> &rowIndexes, int x, int width, bool removeEmptyRows) {
543     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
544 
545     const MultipleAlignment &ma = getMultipleAlignment();
546     QList<qint64> modifiedRowIds = convertMaRowIndexesToMaRowIds(rowIndexes);
547     U2OpStatus2Log os;
548     removeRegionPrivate(os, entityRef, modifiedRowIds, x, width);
549     SAFE_POINT_OP(os, );
550 
551     QList<qint64> removedRowIds;
552     if (removeEmptyRows) {
553         removedRowIds = MsaDbiUtils::removeEmptyRows(entityRef, modifiedRowIds, os);
554         SAFE_POINT_OP(os, );
555         if (!removedRowIds.isEmpty()) {
556             // suppose that if at least one row in msa was removed then all the rows below it were changed
557             QList<qint64> rowIdsAffectedByDeletion = getRowsAffectedByDeletion(ma, removedRowIds);
558             foreach (qint64 removedRowId, removedRowIds) {  // removed rows ain't need to be update
559                 modifiedRowIds.removeAll(removedRowId);
560             }
561             modifiedRowIds = mergeLists(modifiedRowIds, rowIdsAffectedByDeletion);
562         }
563     }
564 
565     MaModificationInfo mi;
566     mi.modifiedRowIds = modifiedRowIds;
567     updateCachedMultipleAlignment(mi, removedRowIds);
568 
569     if (!removedRowIds.isEmpty()) {
570         emit si_rowsRemoved(removedRowIds);
571     }
572 }
573 
removeRegion(int startPos,int startRow,int nBases,int nRows,bool removeEmptyRows,bool track)574 void MultipleAlignmentObject::removeRegion(int startPos, int startRow, int nBases, int nRows, bool removeEmptyRows, bool track) {
575     SAFE_POINT(!isStateLocked(), "Alignment state is locked", );
576 
577     QList<qint64> modifiedRowIds;
578     const MultipleAlignment &ma = getMultipleAlignment();
579     const QList<MultipleAlignmentRow> &maRows = ma->getRows();
580     SAFE_POINT(nRows > 0 && startRow >= 0 && startRow + nRows <= maRows.size() && startPos + nBases <= ma->getLength(), "Invalid parameters", );
581     QList<MultipleAlignmentRow>::ConstIterator it = maRows.begin() + startRow;
582     QList<MultipleAlignmentRow>::ConstIterator end = it + nRows;
583     for (; it != end; it++) {
584         modifiedRowIds << (*it)->getRowId();
585     }
586 
587     U2OpStatus2Log os;
588     removeRegionPrivate(os, entityRef, modifiedRowIds, startPos, nBases);
589     SAFE_POINT_OP(os, );
590 
591     QList<qint64> removedRows;
592     if (removeEmptyRows) {
593         removedRows = MsaDbiUtils::removeEmptyRows(entityRef, modifiedRowIds, os);
594         SAFE_POINT_OP(os, );
595         if (!removedRows.isEmpty()) {  // suppose that if at least one row in msa was removed then
596             // all the rows below it were changed
597             const QList<qint64> rowIdsAffectedByDeletion = getRowsAffectedByDeletion(ma, removedRows);
598             foreach (qint64 removedRowId, removedRows) {  // removed rows ain't need to be update
599                 modifiedRowIds.removeAll(removedRowId);
600             }
601             modifiedRowIds = mergeLists(modifiedRowIds, rowIdsAffectedByDeletion);
602         }
603     }
604 
605     if (track || !removedRows.isEmpty()) {
606         MaModificationInfo mi;
607         mi.modifiedRowIds = modifiedRowIds;
608         updateCachedMultipleAlignment(mi, removedRows);
609     }
610 
611     if (!removedRows.isEmpty()) {
612         emit si_rowsRemoved(removedRows);
613     }
614 }
615 
deleteGap(U2OpStatus & os,const U2Region & rows,int pos,int maxGaps)616 int MultipleAlignmentObject::deleteGap(U2OpStatus &os, const U2Region &rows, int pos, int maxGaps) {
617     QList<int> rowIndexes;
618     for (int i = (int)rows.startPos; i < (int)rows.endPos(); i++) {
619         rowIndexes << i;
620     }
621     return deleteGapByRowIndexList(os, rowIndexes, pos, maxGaps);
622 }
623 
toUniqueRowIndexes(const QList<int> & rowIndexes,int numRows)624 static QList<int> toUniqueRowIndexes(const QList<int> &rowIndexes, int numRows) {
625     QSet<int> uniqueRowIndexes;
626     for (int i = 0; i < rowIndexes.size(); i++) {
627         int rowIndex = rowIndexes[i];
628         if (rowIndex >= 0 && rowIndex < numRows) {
629             uniqueRowIndexes << rowIndex;
630         }
631     }
632     return uniqueRowIndexes.toList();
633 }
634 
deleteGapByRowIndexList(U2OpStatus & os,const QList<int> & rowIndexes,int pos,int maxGaps)635 int MultipleAlignmentObject::deleteGapByRowIndexList(U2OpStatus &os, const QList<int> &rowIndexes, int pos, int maxGaps) {
636     SAFE_POINT(!isStateLocked(), "Alignment state is locked", 0);
637 
638     int removingGapColumnCount = getMaxWidthOfGapRegion(os, rowIndexes, pos, maxGaps);
639     SAFE_POINT_OP(os, 0);
640 
641     if (removingGapColumnCount == 0) {
642         return 0;
643     } else if (removingGapColumnCount < maxGaps) {
644         pos += maxGaps - removingGapColumnCount;
645     }
646     QList<qint64> modifiedRowIds;
647     MultipleAlignment msa = getMultipleAlignmentCopy();
648     // iterate through given rows to update each of them in DB
649     QList<int> uniqueRowIndexes = toUniqueRowIndexes(rowIndexes, getNumRows());
650     for (int i = 0; i < rowIndexes.size(); i++) {
651         int rowIndex = uniqueRowIndexes[i];
652         msa->removeChars(rowIndex, pos, removingGapColumnCount, os);
653         CHECK_OP(os, 0);
654 
655         MultipleAlignmentRow row = msa->getRow(rowIndex);
656         MaDbiUtils::updateRowGapModel(entityRef, row->getRowId(), row->getGapModel(), os);
657         CHECK_OP(os, 0);
658         modifiedRowIds << row->getRowId();
659     }
660 
661     if (uniqueRowIndexes.size() == getNumRows()) {
662         // delete columns
663         MaDbiUtils::updateMaLength(entityRef, getLength() - removingGapColumnCount, os);
664         CHECK_OP(os, 0);
665     }
666 
667     MaModificationInfo mi;
668     mi.rowListChanged = false;
669     mi.modifiedRowIds = modifiedRowIds;
670     updateCachedMultipleAlignment(mi);
671     return removingGapColumnCount;
672 }
673 
shiftRegion(int startPos,int startRow,int nBases,int nRows,int shift)674 int MultipleAlignmentObject::shiftRegion(int startPos, int startRow, int nBases, int nRows, int shift) {
675     SAFE_POINT(!isStateLocked(), "Alignment state is locked", 0);
676     SAFE_POINT(!isRegionEmpty(startPos, startRow, nBases, nRows), "Region is empty", 0);
677     SAFE_POINT(0 <= startPos && 0 <= startRow && 0 < nBases && 0 < nRows, "Invalid parameters of selected region encountered", 0);
678     U2OpStatusImpl os;
679 
680     int n = 0;
681     if (shift > 0) {
682         // if last symbol selected - do not add gaps at the end
683         if (startPos + nBases != getLength()) {
684             // if some trailing gaps are selected --> save them!
685             if (startPos + nBases + shift > getLength()) {
686                 bool increaseAlignmentLen = true;
687                 for (int i = startRow; i < startRow + nRows; i++) {
688                     int rowLen = getRow(i)->getRowLengthWithoutTrailing();
689                     if (rowLen >= startPos + nBases + shift) {
690                         increaseAlignmentLen = false;
691                         break;
692                     }
693                 }
694                 if (increaseAlignmentLen) {
695                     MaDbiUtils::updateMaLength(entityRef, startPos + nBases + shift, os);
696                     SAFE_POINT_OP(os, 0);
697                     updateCachedMultipleAlignment();
698                 }
699             }
700         }
701 
702         insertGap(U2Region(startRow, nRows), startPos, shift);
703         n = shift;
704     } else if (0 < startPos) {
705         if (0 > startPos + shift) {
706             shift = -startPos;
707         }
708         n = -deleteGap(os, U2Region(startRow, nRows), startPos + shift, -shift);
709         SAFE_POINT_OP(os, 0);
710     }
711     return n;
712 }
713 
saveState()714 void MultipleAlignmentObject::saveState() {
715     const MultipleAlignment &ma = getMultipleAlignment();
716     emit si_completeStateChanged(false);
717     savedState.setState(ma);
718 }
719 
releaseState()720 void MultipleAlignmentObject::releaseState() {
721     if (!isStateLocked()) {
722         emit si_completeStateChanged(true);
723 
724         CHECK(savedState.hasState(), );
725         MultipleAlignment maBefore = savedState.takeState();
726         CHECK(*maBefore != *getMultipleAlignment(), );
727         setModified(true);
728 
729         MaModificationInfo mi;
730         emit si_alignmentChanged(maBefore, mi);
731 
732         if (cachedMa->isEmpty() && !maBefore->isEmpty()) {
733             emit si_alignmentBecomesEmpty(true);
734         } else if (!cachedMa->isEmpty() && maBefore->isEmpty()) {
735             emit si_alignmentBecomesEmpty(false);
736         }
737     }
738 }
739 
loadDataCore(U2OpStatus & os)740 void MultipleAlignmentObject::loadDataCore(U2OpStatus &os) {
741     DbiConnection con(entityRef.dbiRef, os);
742     CHECK_OP(os, );
743     loadAlignment(os);
744 }
745 
getMaxWidthOfGapRegion(U2OpStatus & os,const QList<int> & rowIndexes,int pos,int maxGaps)746 int MultipleAlignmentObject::getMaxWidthOfGapRegion(U2OpStatus &os, const QList<int> &rowIndexes, int pos, int maxGaps) {
747     const MultipleAlignment &ma = getMultipleAlignment();
748     SAFE_POINT_EXT(pos >= 0 && maxGaps >= 0 && pos < ma->getLength(), os.setError("Illegal parameters of the gap region"), 0);
749 
750     // check if there is nothing to remove
751     int maxRemovedGaps = qBound(0, maxGaps, ma->getLength() - pos);
752     if (maxRemovedGaps == 0) {
753         return 0;
754     }
755     QList<int> uniqueRowIndexes = toUniqueRowIndexes(rowIndexes, getNumRows());
756     int removingGapColumnCount = maxRemovedGaps;
757     bool isRegionInRowTrailingGaps = true;
758     // iterate through given rows to determine the width of the continuous gap region
759     for (int i = 0; i < uniqueRowIndexes.size(); i++) {
760         int rowIndex = uniqueRowIndexes[i];
761         int gapCountInCurrentRow = 0;
762         // iterate through current row bases to determine gap count
763         while (gapCountInCurrentRow < maxRemovedGaps) {
764             if (!ma->isGap(rowIndex, pos + maxGaps - gapCountInCurrentRow - 1)) {
765                 break;
766             }
767             gapCountInCurrentRow++;
768         }
769 
770         // determine if the given area intersects a row in the area of trailing gaps
771         if (gapCountInCurrentRow != 0 && isRegionInRowTrailingGaps) {
772             int trailingPosition = pos + maxRemovedGaps - gapCountInCurrentRow;
773             if (trailingPosition != ma->getLength()) {
774                 while (ma->getLength() > trailingPosition && isRegionInRowTrailingGaps) {
775                     isRegionInRowTrailingGaps = isRegionInRowTrailingGaps && ma->isGap(rowIndex, trailingPosition);
776                     trailingPosition++;
777                 }
778             }
779         } else if (isRegionInRowTrailingGaps) {
780             isRegionInRowTrailingGaps = false;
781         }
782 
783         if (gapCountInCurrentRow == 0) {
784             // don't do anything if there is a row without gaps
785             return 0;
786         }
787         removingGapColumnCount = qMin(removingGapColumnCount, gapCountInCurrentRow);
788     }
789 
790     if (isRegionInRowTrailingGaps) {
791         if (uniqueRowIndexes.size() == getNumRows()) {
792             return qMin((int)getLength() - pos, maxGaps);
793         } else {
794             return 0;
795         }
796     }
797 
798     return removingGapColumnCount;
799 }
800 
convertMaRowIndexesToMaRowIds(const QList<int> & maRowIndexes,bool excludeErrors)801 QList<qint64> MultipleAlignmentObject::convertMaRowIndexesToMaRowIds(const QList<int> &maRowIndexes, bool excludeErrors) {
802     QList<qint64> ids;
803     const QList<MultipleAlignmentRow> &rows = getMultipleAlignment()->getRows();
804     for (int i = 0; i < maRowIndexes.length(); i++) {
805         int index = maRowIndexes[i];
806         bool isValid = index >= 0 && index <= rows.size() - 1;
807         if (isValid) {
808             ids << rows[index]->getRowId();
809         } else if (!excludeErrors) {
810             ids << -1;
811         }
812     }
813     return ids;
814 }
815 
convertMaRowIdsToMaRowIndexes(const QList<qint64> & maRowIds,bool excludeErrors)816 QList<int> MultipleAlignmentObject::convertMaRowIdsToMaRowIndexes(const QList<qint64> &maRowIds, bool excludeErrors) {
817     QList<int> indexes;
818     const QList<MultipleAlignmentRow> &rows = getMultipleAlignment()->getRows();
819     for (int i = 0; i < maRowIds.length(); i++) {
820         int rowId = maRowIds[i];
821         int index = -1;
822         for (int j = 0; j < rows.size(); j++) {
823             const MultipleAlignmentRow &row = rows[j];
824             if (row->getRowId() == rowId) {
825                 index = j;
826                 break;
827             }
828         }
829         bool isValid = index >= 0;
830         if (isValid) {
831             indexes << index;
832         } else if (!excludeErrors) {
833             indexes << -1;
834         }
835     }
836     return indexes;
837 }
838 
839 }  // namespace U2
840