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 "ModifySequenceObjectTask.h"
23 
24 #include <U2Core/AddDocumentTask.h>
25 #include <U2Core/AnnotationTableObject.h>
26 #include <U2Core/AppContext.h>
27 #include <U2Core/Counter.h>
28 #include <U2Core/DNAAlphabet.h>
29 #include <U2Core/DNASequenceObject.h>
30 #include <U2Core/DNATranslation.h>
31 #include <U2Core/DocumentModel.h>
32 #include <U2Core/GObject.h>
33 #include <U2Core/GObjectRelationRoles.h>
34 #include <U2Core/GObjectUtils.h>
35 #include <U2Core/IOAdapterUtils.h>
36 #include <U2Core/Log.h>
37 #include <U2Core/MultiTask.h>
38 #include <U2Core/ProjectModel.h>
39 #include <U2Core/SaveDocumentTask.h>
40 #include <U2Core/U2ObjectDbi.h>
41 #include <U2Core/U2SafePoints.h>
42 #include <U2Core/U2SequenceUtils.h>
43 
44 namespace U2 {
45 
46 typedef QPair<QString, QString> QStrStrPair;
47 
ModifySequenceContentTask(const DocumentFormatId & dfId,U2SequenceObject * seqObj,const U2Region & regionTodelete,const DNASequence & seq2Insert,bool recalculateQualifiers,U1AnnotationUtils::AnnotationStrategyForResize str,const GUrl & url,bool mergeAnnotations)48 ModifySequenceContentTask::ModifySequenceContentTask(const DocumentFormatId &dfId, U2SequenceObject *seqObj, const U2Region &regionTodelete, const DNASequence &seq2Insert, bool recalculateQualifiers, U1AnnotationUtils::AnnotationStrategyForResize str, const GUrl &url, bool mergeAnnotations)
49     : Task(tr("Modify sequence task"), TaskFlags(TaskFlag_NoRun) | TaskFlag_ReportingIsSupported), resultFormatId(dfId),
50       mergeAnnotations(mergeAnnotations), recalculateQualifiers(recalculateQualifiers), curDoc(seqObj->getDocument()), newDoc(nullptr), url(url), strat(str),
51       seqObj(seqObj), regionToReplace(regionTodelete), sequence2Insert(seq2Insert) {
52     GCOUNTER(cvar, "Modify sequence task");
53     inplaceMod = url == curDoc->getURL() || url.isEmpty();
54 }
55 
report()56 Task::ReportResult ModifySequenceContentTask::report() {
57     CHECK(!(regionToReplace.isEmpty() && sequence2Insert.seq.isEmpty()), ReportResult_Finished);
58     CHECK_EXT(!curDoc->isStateLocked(), setError(tr("Document is locked")), ReportResult_Finished);
59 
60     U2Region seqRegion(0, seqObj->getSequenceLength());
61     if (!seqRegion.contains(regionToReplace)) {
62         algoLog.error(tr("Region to delete is larger than the whole sequence"));
63         return ReportResult_Finished;
64     }
65 
66     Project *project = AppContext::getProject();
67     if (project != nullptr) {
68         CHECK(!project->isStateLocked(), ReportResult_CallMeAgain);
69         docs = project->getDocuments();
70     }
71 
72     if (!docs.contains(curDoc)) {
73         docs.append(curDoc);
74     }
75 
76     if (!inplaceMod) {
77         cloneSequenceAndAnnotations();
78     }
79     seqObj->replaceRegion(regionToReplace, sequence2Insert, stateInfo);
80     CHECK_OP(stateInfo, ReportResult_Finished);
81 
82     annotationForReport = FixAnnotationsUtils::fixAnnotations(&stateInfo, seqObj, regionToReplace, sequence2Insert, recalculateQualifiers, strat, docs);
83     if (!annotationForReport.isEmpty()) {
84         setReportingEnabled(true);
85     }
86 
87     if (!inplaceMod) {
88         QList<Task *> tasks;
89         IOAdapterFactory *iof = AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(IOAdapterUtils::url2io(url));
90         tasks.append(new SaveDocumentTask(seqObj->getDocument(), iof, url.getURLString()));
91         if (project != nullptr) {
92             tasks.append(new AddDocumentTask(newDoc));
93         }
94         AppContext::getTaskScheduler()->registerTopLevelTask(new MultiTask("Save document and add it to project (optional)", tasks));
95     }
96     return ReportResult_Finished;
97 }
98 
99 namespace {
100 
formatPairList(const QList<QStrStrPair> & pairList,bool useFirst)101 QString formatPairList(const QList<QStrStrPair> &pairList, bool useFirst) {
102     QString result;
103     const QString lineSeparator = "<br>";
104     foreach (const QStrStrPair &pair, pairList) {
105         result += useFirst ? pair.first : pair.second;
106         result += lineSeparator;
107     }
108     result.chop(lineSeparator.length());
109     return result;
110 }
111 
112 }  // namespace
113 
generateReport() const114 QString ModifySequenceContentTask::generateReport() const {
115     CHECK(!annotationForReport.isEmpty(), QString());
116 
117     QString report = tr("Some annotations have qualifiers referring a sequence region that has been removed during the sequence editing. "
118                         "You might want to change the qualifiers manually. Find them in the table below");
119     report += "<br><table border=\"1\" cellpadding=\"1\">";
120     report += "<tr><th>";
121     report += tr("Annotation Name");
122     report += "</th><th>";
123     report += tr("Annotation Location");
124     report += "</th><th>";
125     report += tr("Qualifier Name");
126     report += "</th><th>";
127     report += tr("Referenced Region");
128     report += "</th></tr>";
129 
130     foreach (Annotation *an, annotationForReport.keys()) {
131         if (annotationForReport[an].isEmpty()) {
132             coreLog.error(tr("Unexpected qualifiers count"));
133             assert(false);
134             continue;
135         }
136 
137         report += QString("<tr><td>%1</td><td>%2</td>").arg(an->getName()).arg(U1AnnotationUtils::buildLocationString(*an->getLocation()));
138 
139         report += QString("<td>%1</td>").arg(formatPairList(annotationForReport[an], true));
140         report += QString("<td>%1</td>").arg(formatPairList(annotationForReport[an], false));
141 
142         report += "</tr>";
143     }
144     report += "</table>";
145     return report;
146 }
147 
getSequenceLengthDelta() const148 qint64 ModifySequenceContentTask::getSequenceLengthDelta() const {
149     return sequence2Insert.length() + regionToReplace.length;
150 }
151 
cloneSequenceAndAnnotations()152 void ModifySequenceContentTask::cloneSequenceAndAnnotations() {
153     IOAdapterRegistry *ioReg = AppContext::getIOAdapterRegistry();
154     IOAdapterFactory *iof = ioReg->getIOAdapterFactoryById(IOAdapterUtils::url2io(url));
155     CHECK(iof != nullptr, );
156     DocumentFormatRegistry *dfReg = AppContext::getDocumentFormatRegistry();
157     DocumentFormat *df = dfReg->getFormatById(resultFormatId);
158     SAFE_POINT(df != nullptr, "Invalid document format!", );
159 
160     U2SequenceObject *oldSeqObj = seqObj;
161     newDoc = df->createNewLoadedDocument(iof, url, stateInfo, curDoc->getGHintsMap());
162     CHECK_OP(stateInfo, );
163 
164     SAFE_POINT_EXT(df->isObjectOpSupported(newDoc, DocumentFormat::DocObjectOp_Add, GObjectTypes::SEQUENCE),
165                    stateInfo.setError(tr("Failed to add sequence object to document!")), );
166 
167     U2Sequence clonedSeq = U2SequenceUtils::copySequence(oldSeqObj->getSequenceRef(), newDoc->getDbiRef(), U2ObjectDbi::ROOT_FOLDER, stateInfo);
168     CHECK_OP(stateInfo, );
169 
170     seqObj = new U2SequenceObject(oldSeqObj->getGObjectName(), U2EntityRef(newDoc->getDbiRef(), clonedSeq.id), oldSeqObj->getGHintsMap());
171     newDoc->addObject(seqObj);
172 
173     if (df->isObjectOpSupported(newDoc, DocumentFormat::DocObjectOp_Add, GObjectTypes::ANNOTATION_TABLE)) {
174         if (mergeAnnotations) {
175             AnnotationTableObject *newDocAto = new AnnotationTableObject("Annotations", newDoc->getDbiRef());
176             newDocAto->addObjectRelation(seqObj, ObjectRole_Sequence);
177 
178             for (Document *d : qAsConst(docs)) {
179                 QList<GObject *> annotationTablesList = d->findGObjectByType(GObjectTypes::ANNOTATION_TABLE);
180                 for (GObject *table : qAsConst(annotationTablesList)) {
181                     auto ato = qobject_cast<AnnotationTableObject *>(table);
182                     if (ato != nullptr && ato->hasObjectRelation(oldSeqObj, ObjectRole_Sequence)) {
183                         foreach (Annotation *ann, ato->getAnnotations()) {
184                             newDocAto->addAnnotations(QList<SharedAnnotationData>() << ann->getData(), ann->getGroup()->getName());
185                         }
186                     }
187                 }
188             }
189             newDoc->addObject(newDocAto);
190         } else {
191             // use only sequence-doc annotations
192             foreach (GObject *o, curDoc->getObjects()) {
193                 if (auto aObj = qobject_cast<AnnotationTableObject *>(o)) {
194                     GObject *cl = aObj->clone(newDoc->getDbiRef(), stateInfo);
195                     CHECK_OP(stateInfo, );
196                     newDoc->addObject(cl);
197                     GObjectUtils::updateRelationsURL(cl, curDoc->getURL(), newDoc->getURL());
198                 }
199             }
200         }
201     }
202     docs.append(newDoc);
203 }
204 
205 }  // namespace U2
206