96ac7e8e84c7cc8940bcd901fbe128260986289e
[jalview.git] / src / jalview / ws / DBRefFetcher.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)\r
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  * \r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  * \r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  * \r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.ws;\r
20 \r
21 import java.io.*;\r
22 import java.util.*;\r
23 \r
24 import org.exolab.castor.mapping.*;\r
25 import org.exolab.castor.xml.*;\r
26 import jalview.analysis.*;\r
27 import jalview.datamodel.*;\r
28 import jalview.datamodel.Mapping;\r
29 import jalview.gui.*;\r
30 import jalview.ws.dbsources.Uniprot;\r
31 import jalview.ws.ebi.EBIFetchClient;\r
32 \r
33 /**\r
34  * Implements a runnable for validating a sequence against external databases\r
35  * and then propagating references and features onto the sequence(s)\r
36  * \r
37  * @author $author$\r
38  * @version $Revision$\r
39  */\r
40 public class DBRefFetcher implements Runnable\r
41 {\r
42   SequenceI[] dataset;\r
43 \r
44   IProgressIndicator af;\r
45 \r
46   CutAndPasteTransfer output = new CutAndPasteTransfer();\r
47 \r
48   StringBuffer sbuffer = new StringBuffer();\r
49 \r
50   boolean running = false;\r
51 \r
52   // /This will be a collection of Vectors of sequenceI refs.\r
53   // The key will be the seq name or accession id of the seq\r
54   Hashtable seqRefs;\r
55 \r
56   String[] dbSources;\r
57 \r
58   SequenceFetcher sfetcher;\r
59 \r
60   public DBRefFetcher()\r
61   {\r
62   }\r
63 \r
64   /**\r
65    * Creates a new SequenceFeatureFetcher object.\r
66    * \r
67    * @param seqs\r
68    *                fetch references for these sequences\r
69    * @param af\r
70    *                the parent alignframe for progress bar monitoring.\r
71    */\r
72   public DBRefFetcher(SequenceI[] seqs, AlignFrame af)\r
73   {\r
74     this.af = af;\r
75     SequenceI[] ds = new SequenceI[seqs.length];\r
76     for (int i = 0; i < seqs.length; i++)\r
77     {\r
78       if (seqs[i].getDatasetSequence() != null)\r
79         ds[i] = seqs[i].getDatasetSequence();\r
80       else\r
81         ds[i] = seqs[i];\r
82     }\r
83     this.dataset = ds;\r
84     // TODO Jalview 2.5 lots of this code should be in the gui package!\r
85     sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton(af);\r
86     // select appropriate databases based on alignFrame context.\r
87     if (af.getViewport().getAlignment().isNucleotide())\r
88     {\r
89       dbSources = DBRefSource.DNACODINGDBS;\r
90     }\r
91     else\r
92     {\r
93       dbSources = DBRefSource.PROTEINDBS;\r
94     }\r
95   }\r
96 \r
97   /**\r
98    * start the fetcher thread\r
99    * \r
100    * @param waitTillFinished\r
101    *                true to block until the fetcher has finished\r
102    */\r
103   public void fetchDBRefs(boolean waitTillFinished)\r
104   {\r
105     Thread thread = new Thread(this);\r
106     thread.start();\r
107     running = true;\r
108 \r
109     if (waitTillFinished)\r
110     {\r
111       while (running)\r
112       {\r
113         try\r
114         {\r
115           Thread.sleep(500);\r
116         } catch (Exception ex)\r
117         {\r
118         }\r
119       }\r
120     }\r
121   }\r
122 \r
123   /**\r
124    * The sequence will be added to a vector of sequences belonging to key which\r
125    * could be either seq name or dbref id\r
126    * \r
127    * @param seq\r
128    *                SequenceI\r
129    * @param key\r
130    *                String\r
131    */\r
132   void addSeqId(SequenceI seq, String key)\r
133   {\r
134     key = key.toUpperCase();\r
135 \r
136     Vector seqs;\r
137     if (seqRefs.containsKey(key))\r
138     {\r
139       seqs = (Vector) seqRefs.get(key);\r
140 \r
141       if (seqs != null && !seqs.contains(seq))\r
142       {\r
143         seqs.addElement(seq);\r
144       }\r
145       else if (seqs == null)\r
146       {\r
147         seqs = new Vector();\r
148         seqs.addElement(seq);\r
149       }\r
150 \r
151     }\r
152     else\r
153     {\r
154       seqs = new Vector();\r
155       seqs.addElement(seq);\r
156     }\r
157 \r
158     seqRefs.put(key, seqs);\r
159   }\r
160 \r
161   /**\r
162    * DOCUMENT ME!\r
163    */\r
164   public void run()\r
165   {\r
166     if (dbSources == null)\r
167     {\r
168       throw new Error("Implementation error. Must initialise dbSources");\r
169     }\r
170     long startTime = System.currentTimeMillis();\r
171     af.setProgressBar("Fetching db refs", startTime);\r
172     running = true;\r
173     int db = 0;\r
174     Vector sdataset = new Vector();\r
175     for (int s = 0; s < dataset.length; s++)\r
176     {\r
177       sdataset.addElement(dataset[s]);\r
178     }\r
179     while (sdataset.size() > 0 && db < dbSources.length)\r
180     {\r
181       int maxqlen = 1; // default number of queries made to at one time\r
182       System.err.println("Verifying against " + dbSources[db]);\r
183       jalview.ws.seqfetcher.DbSourceProxy dbsource = sfetcher\r
184               .getSourceProxy(dbSources[db]);\r
185       if (dbsource == null)\r
186       {\r
187         System.err.println("No proxy for " + dbSources[db]);\r
188         db++;\r
189         continue;\r
190       }\r
191       if (dbsource.getDbSourceProperties()\r
192               .containsKey(DBRefSource.MULTIACC))\r
193       {\r
194         maxqlen = ((Integer) dbsource.getDbSourceProperties().get(\r
195                 DBRefSource.MULTIACC)).intValue();\r
196       }\r
197       else\r
198       {\r
199         maxqlen = 1;\r
200       }\r
201       // iterate through db for each remaining un-verified sequence\r
202       SequenceI[] currSeqs = new SequenceI[sdataset.size()];\r
203       sdataset.copyInto(currSeqs);// seqs that are to be validated against\r
204       // dbSources[db]\r
205       Vector queries = new Vector(); // generated queries curSeq\r
206       seqRefs = new Hashtable();\r
207 \r
208       int seqIndex = 0;\r
209 \r
210       while (queries.size() > 0 || seqIndex < currSeqs.length)\r
211       {\r
212         if (queries.size() > 0)\r
213         {\r
214           // Still queries to make for current seqIndex\r
215           StringBuffer queryString = new StringBuffer("");\r
216           int numq=0,nqSize = (maxqlen > queries.size()) ? queries.size()\r
217                   : maxqlen;\r
218           \r
219           while (queries.size()>0 && numq < nqSize)\r
220           {\r
221             String query = (String) queries.elementAt(0);\r
222             if (dbsource.isValidReference(query))\r
223             {\r
224               queryString.append((numq == 0) ? "" : dbsource\r
225                       .getAccessionSeparator());\r
226               queryString.append(query);\r
227               numq++;\r
228             }\r
229             // remove the extracted query string\r
230             queries.removeElementAt(0);\r
231           }\r
232           // make the queries and process the response\r
233           AlignmentI retrieved = null;\r
234           try\r
235           {\r
236             if (jalview.bin.Cache.log.isDebugEnabled())\r
237             {\r
238               jalview.bin.Cache.log.debug("Querying "+dbsource.getDbName()+" with : '"+queryString.toString()+"'");\r
239             }\r
240             retrieved = dbsource.getSequenceRecords(queryString.toString());\r
241           } catch (Exception ex)\r
242           {\r
243             ex.printStackTrace();\r
244           } catch (OutOfMemoryError err)\r
245           {\r
246             new OOMWarning("retrieving database references ("\r
247                     + queryString.toString() + ")", err);\r
248           }\r
249           if (retrieved != null)\r
250           {\r
251             transferReferences(sdataset, dbSources[db], retrieved);\r
252           }\r
253         }\r
254         else\r
255         {\r
256           // make some more strings for use as queries\r
257           for (int i = 0; (seqIndex < dataset.length) && (i < 50); seqIndex++, i++)\r
258           {\r
259             SequenceI sequence = dataset[seqIndex];\r
260             DBRefEntry[] uprefs = jalview.util.DBRefUtils.selectRefs(\r
261                     sequence.getDBRef(), new String[]\r
262                     { dbSources[db] }); // jalview.datamodel.DBRefSource.UNIPROT\r
263             // });\r
264             // check for existing dbrefs to use\r
265             if (uprefs != null && uprefs.length>0)\r
266             {\r
267               for (int j = 0; j < uprefs.length; j++)\r
268               {\r
269                 addSeqId(sequence, uprefs[j].getAccessionId());\r
270                 queries\r
271                         .addElement(uprefs[j].getAccessionId()\r
272                                 .toUpperCase());\r
273               }\r
274             }\r
275             else\r
276             {\r
277               // generate queries from sequence ID string\r
278               StringTokenizer st = new StringTokenizer(sequence.getName(),\r
279                       "|");\r
280               while (st.hasMoreTokens())\r
281               {\r
282                 String token = st.nextToken();\r
283                 addSeqId(sequence, token);\r
284                 queries.addElement(token.toUpperCase());\r
285               }\r
286             }\r
287           }\r
288         }\r
289       }\r
290       // advance to next database\r
291       db++;\r
292     } // all databases have been queries.\r
293     if (sbuffer.length() > 0)\r
294     {\r
295       output\r
296               .setText("Your sequences have been verified against known sequence databases. Some of the ids have been\n"\r
297                       + "altered, most likely the start/end residue will have been updated.\n"\r
298                       + "Save your alignment to maintain the updated id.\n\n"\r
299                       + sbuffer.toString());\r
300       Desktop.addInternalFrame(output, "Sequence names updated ", 600, 300);\r
301       // The above is the dataset, we must now find out the index\r
302       // of the viewed sequence\r
303 \r
304     }\r
305 \r
306     af.setProgressBar("DBRef search completed", startTime);\r
307     // promptBeforeBlast();\r
308 \r
309     running = false;\r
310 \r
311   }\r
312 \r
313   /**\r
314    * Verify local sequences in seqRefs against the retrieved sequence database\r
315    * records.\r
316    * \r
317    */\r
318   void transferReferences(Vector sdataset, String dbSource,\r
319           AlignmentI retrievedAl) // File\r
320   // file)\r
321   {\r
322 \r
323     if (retrievedAl == null || retrievedAl.getHeight() == 0)\r
324     {\r
325       return;\r
326     }\r
327     SequenceI[] retrieved = retrievedAl.getSequencesArray();\r
328     SequenceI sequence = null;\r
329 \r
330     // Vector entries = new Uniprot().getUniprotEntries(file);\r
331 \r
332     int i, iSize = retrieved.length; // entries == null ? 0 : entries.size();\r
333     // UniprotEntry entry;\r
334     for (i = 0; i < iSize; i++)\r
335     {\r
336       SequenceI entry = retrieved[i]; // (UniprotEntry) entries.elementAt(i);\r
337 \r
338       // Work out which sequences this sequence matches,\r
339       // taking into account all accessionIds and names in the file\r
340       Vector sequenceMatches = new Vector();\r
341       // look for corresponding accession ids\r
342       DBRefEntry[] entryRefs = jalview.util.DBRefUtils.selectRefs(entry\r
343               .getDBRef(), new String[]\r
344       { dbSource });\r
345       for (int j = 0; j < entryRefs.length; j++)\r
346       {\r
347         String accessionId = entryRefs[j].getAccessionId(); // .getAccession().elementAt(j).toString();\r
348         // match up on accessionId\r
349         if (seqRefs.containsKey(accessionId.toUpperCase()))\r
350         {\r
351           Vector seqs = (Vector) seqRefs.get(accessionId);\r
352           for (int jj = 0; jj < seqs.size(); jj++)\r
353           {\r
354             sequence = (SequenceI) seqs.elementAt(jj);\r
355             if (!sequenceMatches.contains(sequence))\r
356             {\r
357               sequenceMatches.addElement(sequence);\r
358             }\r
359           }\r
360         }\r
361       }\r
362       if (sequenceMatches.size() == 0)\r
363       {\r
364         // failed to match directly on accessionId==query so just compare all\r
365         // sequences to entry\r
366         Enumeration e = seqRefs.keys();\r
367         while (e.hasMoreElements())\r
368         {\r
369           Vector sqs = (Vector) seqRefs.get(e.nextElement());\r
370           if (sqs != null && sqs.size() > 0)\r
371           {\r
372             Enumeration sqe = sqs.elements();\r
373             while (sqe.hasMoreElements())\r
374             {\r
375               sequenceMatches.addElement(sqe.nextElement());\r
376             }\r
377           }\r
378         }\r
379       }\r
380       // look for corresponding names\r
381       // this is uniprot specific ?\r
382       // could be useful to extend this so we try to find any 'significant'\r
383       // information in common between two sequence objects.\r
384       /*\r
385        * DBRefEntry[] entryRefs =\r
386        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {\r
387        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String\r
388        * name = entry.getName().elementAt(j).toString(); if\r
389        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);\r
390        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)\r
391        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {\r
392        * sequenceMatches.addElement(sequence); } } } }\r
393        */\r
394       // sequenceMatches now contains the set of all sequences associated with\r
395       // the returned db record\r
396       String entrySeq = entry.getSequenceAsString().toUpperCase();\r
397       for (int m = 0; m < sequenceMatches.size(); m++)\r
398       {\r
399         sequence = (SequenceI) sequenceMatches.elementAt(m);\r
400         // only update start and end positions and shift features if there are\r
401         // no existing references\r
402         // TODO: test for legacy where uniprot or EMBL refs exist but no\r
403         // mappings are made (but content matches retrieved set)\r
404         boolean updateRefFrame = sequence.getDBRef() == null\r
405                 || sequence.getDBRef().length == 0;\r
406         // verify sequence against the entry sequence\r
407 \r
408         String nonGapped = AlignSeq.extractGaps("-. ",\r
409                 sequence.getSequenceAsString()).toUpperCase();\r
410 \r
411         int absStart = entrySeq.indexOf(nonGapped);\r
412         int mapStart = entry.getStart();\r
413         jalview.datamodel.Mapping mp;\r
414 \r
415         if (absStart == -1)\r
416         {\r
417           // Is local sequence contained in dataset sequence?\r
418           absStart = nonGapped.indexOf(entrySeq);\r
419           if (absStart == -1)\r
420           { // verification failed.\r
421             sbuffer.append(sequence.getName()\r
422                     + " SEQUENCE NOT %100 MATCH \n");\r
423             continue;\r
424           }\r
425 \r
426           sbuffer.append(sequence.getName() + " HAS " + absStart\r
427                   + " PREFIXED RESIDUES COMPARED TO " + dbSource + "\n");\r
428           //\r
429           // + " - ANY SEQUENCE FEATURES"\r
430           // + " HAVE BEEN ADJUSTED ACCORDINGLY \n");\r
431           // absStart = 0;\r
432           // create valid mapping between matching region of local sequence and\r
433           // the mapped sequence\r
434           mp = new Mapping(null, new int[]\r
435           { sequence.getStart() + absStart,\r
436               sequence.getStart() + absStart + entrySeq.length() - 1 },\r
437                   new int[]\r
438                   { entry.getStart(),\r
439                       entry.getStart() + entrySeq.length() - 1 }, 1, 1);\r
440           updateRefFrame = false; // mapping is based on current start/end so\r
441           // don't modify start and end\r
442         }\r
443         else\r
444         {\r
445           // update start and end of local sequence to place it in entry's\r
446           // reference frame.\r
447           // apply identity map map from whole of local sequence to matching\r
448           // region of database\r
449           // sequence\r
450           mp = null; // Mapping.getIdentityMap();\r
451           // new Mapping(null,\r
452           // new int[] { absStart+sequence.getStart(),\r
453           // absStart+sequence.getStart()+entrySeq.length()-1},\r
454           // new int[] { entry.getStart(), entry.getEnd() }, 1, 1);\r
455           // relocate local features for updated start\r
456           if (updateRefFrame && sequence.getSequenceFeatures() != null)\r
457           {\r
458             SequenceFeature[] sf = sequence.getSequenceFeatures();\r
459             int start = sequence.getStart();\r
460             int end = sequence.getEnd();\r
461             int startShift = 1 - absStart - start; // how much the features are\r
462             // to be shifted by\r
463             for (int sfi = 0; sfi < sf.length; sfi++)\r
464             {\r
465               if (sf[sfi].getBegin() >= start && sf[sfi].getEnd() <= end)\r
466               {\r
467                 // shift feature along by absstart\r
468                 sf[sfi].setBegin(sf[sfi].getBegin() + startShift);\r
469                 sf[sfi].setEnd(sf[sfi].getEnd() + startShift);\r
470               }\r
471             }\r
472           }\r
473         }\r
474 \r
475         System.out.println("Adding dbrefs to " + sequence.getName()\r
476                 + " from " + dbSource + " sequence : " + entry.getName());\r
477         sequence.transferAnnotation(entry, mp);\r
478         // unknownSequences.remove(sequence);\r
479         int absEnd = absStart + nonGapped.length();\r
480         absStart += 1;\r
481         if (updateRefFrame)\r
482         {\r
483           // finally, update local sequence reference frame if we're allowed\r
484           sequence.setStart(absStart);\r
485           sequence.setEnd(absEnd);\r
486         }\r
487         // and remove it from the rest\r
488         // TODO: decide if we should remove annotated sequence from set\r
489         sdataset.remove(sequence);\r
490       }\r
491     }\r
492   }\r
493 }\r