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