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