f319eb12e52fec1f87aef97631f56296f83b7012
[jalview.git] / src / jalview / util / GroupUrlLink.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.util;
20
21 import jalview.datamodel.Sequence;
22 import jalview.datamodel.SequenceI;
23
24 import java.util.Hashtable;
25 import java.util.Vector;
26
27 public class GroupUrlLink
28 {
29   /**
30    * Helper class based on the UrlLink class which enables URLs to be
31    * constructed from sequences or IDs associated with a group of sequences. URL
32    * definitions consist of a pipe separated string containing a <label>|<url
33    * construct>|<separator character>[|<sequence separator character>]. The url
34    * construct includes regex qualified tokens which are replaced with seuqence
35    * IDs ($SEQUENCE_IDS$) and/or seuqence regions ($SEQUENCES$) that are
36    * extracted from the group. See <code>UrlLink</code> for more information
37    * about the approach, and the original implementation.
38    * 
39    */
40   private String url_prefix, target, label;
41
42   /**
43    * these are all filled in order of the occurence of each token in the url
44    * string template
45    */
46   private String url_suffix[], separators[], regexReplace[];
47
48   private String invalidMessage = null;
49
50   /**
51    * tokens that can be replaced in the URL.
52    */
53   private static String[] tokens;
54
55   /**
56    * position of each token (which can appear once only) in the url
57    */
58   private int[] segs;
59
60   /**
61    * contains tokens in the order they appear in the URL template.
62    */
63   private String[] mtch;
64   static
65   {
66     if (tokens == null)
67     {
68       tokens = new String[]
69       { "SEQUENCEIDS", "SEQUENCES", "DATASETID" };
70     }
71   }
72
73   // private int idseg = -1, seqseg = -1;
74
75   /**
76    * parse the given linkString of the form '<label>|<url>|separator
77    * char[|optional sequence separator char]' into parts. url may contain a
78    * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
79    * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
80    * $SEQUENCES<=optional regex=>$.
81    * 
82    * @param link
83    */
84   public GroupUrlLink(String link)
85   {
86     int sep = link.indexOf("|");
87     segs = new int[tokens.length];
88     int ntoks = 0;
89     for (int i = 0; i < segs.length; i++)
90     {
91       if ((segs[i] = link.indexOf("$" + tokens[i])) > -1)
92       {
93         ntoks++;
94       }
95     }
96     // expect at least one token
97     if (ntoks == 0)
98     {
99       invalidMessage = "Group URL string must contain at least one of ";
100       for (int i = 0; i < segs.length; i++)
101       {
102         invalidMessage += " '$" + tokens[i] + "[=/regex=/]$'";
103       }
104       return;
105     }
106
107     int[] ptok = new int[ntoks + 1];
108     String[] tmtch = new String[ntoks + 1];
109     mtch = new String[ntoks];
110     for (int i = 0, t = 0; i < segs.length; i++)
111     {
112       if (segs[i] > -1)
113       {
114         ptok[t] = segs[i];
115         tmtch[t++] = tokens[i];
116       }
117     }
118     ptok[ntoks] = link.length();
119     tmtch[ntoks] = "$$$$$$$$$";
120     jalview.util.QuickSort.sort(ptok, tmtch);
121     for (int i = 0; i < ntoks; i++)
122     {
123       mtch[i] = tmtch[i]; // TODO: check order is ascending
124     }
125     /*
126      * replaces the specific code below {}; if (psqids > -1 && pseqs > -1) { if
127      * (psqids > pseqs) { idseg = 1; seqseg = 0;
128      * 
129      * ptok = new int[] { pseqs, psqids, link.length() }; mtch = new String[] {
130      * "$SEQUENCES", "$SEQUENCEIDS" }; } else { idseg = 0; seqseg = 1; ptok =
131      * new int[] { psqids, pseqs, link.length() }; mtch = new String[] {
132      * "$SEQUENCEIDS", "$SEQUENCES" }; } } else { if (psqids != -1) { idseg = 0;
133      * ptok = new int[] { psqids, link.length() }; mtch = new String[] {
134      * "$SEQUENCEIDS" }; } else { seqseg = 0; ptok = new int[] { pseqs,
135      * link.length() }; mtch = new String[] { "$SEQUENCES" }; } }
136      */
137
138     int p = sep;
139     // first get the label and target part before the first |
140     do
141     {
142       sep = p;
143       p = link.indexOf("|", sep + 1);
144     } while (p > sep && p < ptok[0]);
145     // Assuming that the URL itself does not contain any '|' symbols
146     // sep now contains last pipe symbol position prior to any regex symbols
147     label = link.substring(0, sep);
148     if (label.indexOf("|") > -1)
149     {
150       // | terminated database name / www target at start of Label
151       target = label.substring(0, label.indexOf("|"));
152     }
153     else if (label.indexOf(" ") > 2)
154     {
155       // space separated Label - matches database name
156       target = label.substring(0, label.indexOf(" "));
157     }
158     else
159     {
160       target = label;
161     }
162     // Now Parse URL : Whole URL string first
163     url_prefix = link.substring(sep + 1, ptok[0]);
164     url_suffix = new String[mtch.length];
165     regexReplace = new String[mtch.length];
166     // and loop through tokens
167     for (int pass = 0; pass < mtch.length; pass++)
168     {
169       int mlength = 3 + mtch[pass].length();
170       if (link.indexOf("$" + mtch[pass] + "=/") == ptok[pass]
171               && (p = link.indexOf("/=$", ptok[pass] + mlength)) > ptok[pass]
172                       + mlength)
173       {
174         // Extract Regex and suffix
175         if (ptok[pass + 1] < p + 3)
176         {
177           // tokens are not allowed inside other tokens - e.g. inserting a
178           // $sequences$ into the regex match for the sequenceid
179           invalidMessage = "Token regexes cannot contain other regexes (did you terminate the $"
180                   + mtch[pass] + " regex with a '/=$' ?";
181           return;
182         }
183         url_suffix[pass] = link.substring(p + 3, ptok[pass + 1]);
184         regexReplace[pass] = link.substring(ptok[pass] + mlength, p);
185         try
186         {
187           com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
188                   + regexReplace[pass] + "/");
189           if (rg == null)
190           {
191             invalidMessage = "Invalid Regular Expression : '"
192                     + regexReplace[pass] + "'\n";
193           }
194         } catch (Exception e)
195         {
196           invalidMessage = "Invalid Regular Expression : '"
197                   + regexReplace[pass] + "'\n";
198         }
199       }
200       else
201       {
202         regexReplace[pass] = null;
203         // verify format is really correct.
204         if ((p = link.indexOf("$" + mtch[pass] + "$")) == ptok[pass])
205         {
206           url_suffix[pass] = link.substring(p + mtch[pass].length() + 2,
207                   ptok[pass + 1]);
208         }
209         else
210         {
211           invalidMessage = "Warning: invalid regex structure (after '"
212                   + mtch[0] + "') for URL link : " + link;
213         }
214       }
215     }
216     int pass = 0;
217     separators = new String[url_suffix.length];
218     String suffices = url_suffix[url_suffix.length - 1], lastsep = ",";
219     // have a look in the last suffix for any more separators.
220     while ((p = suffices.indexOf('|')) > -1)
221     {
222       separators[pass] = suffices.substring(p + 1);
223       if (pass == 0)
224       {
225         // trim the original suffix string
226         url_suffix[url_suffix.length - 1] = suffices.substring(0, p);
227       }
228       else
229       {
230         lastsep = (separators[pass - 1] = separators[pass - 1].substring(0,
231                 p));
232       }
233       suffices = separators[pass];
234       pass++;
235     }
236     if (pass > 0)
237     {
238       lastsep = separators[pass - 1];
239     }
240     // last separator is always used for all the remaining separators
241     while (pass < separators.length)
242     {
243       separators[pass++] = lastsep;
244     }
245   }
246
247   /**
248    * @return the url_suffix
249    */
250   public String getUrl_suffix()
251   {
252     return url_suffix[url_suffix.length - 1];
253   }
254
255   /**
256    * @return the url_prefix
257    */
258   public String getUrl_prefix()
259   {
260     return url_prefix;
261   }
262
263   /**
264    * @return the target
265    */
266   public String getTarget()
267   {
268     return target;
269   }
270
271   /**
272    * @return the label
273    */
274   public String getLabel()
275   {
276     return label;
277   }
278
279   /**
280    * @return the sequence ID regexReplace
281    */
282   public String getIDRegexReplace()
283   {
284     return _replaceFor(tokens[0]);
285   }
286
287   private String _replaceFor(String token)
288   {
289     for (int i = 0; i < mtch.length; i++)
290       if (segs[i] > -1 && mtch[i].equals(token))
291       {
292         return regexReplace[i];
293       }
294     return null;
295   }
296
297   /**
298    * @return the sequence ID regexReplace
299    */
300   public String getSeqRegexReplace()
301   {
302     return _replaceFor(tokens[1]);
303   }
304
305   /**
306    * @return the invalidMessage
307    */
308   public String getInvalidMessage()
309   {
310     return invalidMessage;
311   }
312
313   /**
314    * Check if URL string was parsed properly.
315    * 
316    * @return boolean - if false then <code>getInvalidMessage</code> returns an
317    *         error message
318    */
319   public boolean isValid()
320   {
321     return invalidMessage == null;
322   }
323
324   /**
325    * return one or more URL strings by applying regex to the given idstring
326    * 
327    * @param idstrings
328    *          array of id strings to pass to service
329    * @param seqstrings
330    *          array of seq strings to pass to service
331    * @param onlyIfMatches
332    *          - when true url strings are only made if regex is defined and
333    *          matches for all qualified tokens in groupURL - TODO: consider if
334    *          onlyIfMatches is really a useful parameter!
335    * @return null or Object[] { int[] { number of seqs substituted},boolean[] {
336    *         which seqs were substituted }, StringBuffer[] { substituted lists
337    *         for each token }, String[] { url } }
338    */
339   public Object[] makeUrls(String[] idstrings, String[] seqstrings,
340           String dsstring, boolean onlyIfMatches)
341   {
342     Hashtable rstrings = new Hashtable();
343     rstrings.put(tokens[0], idstrings);
344     rstrings.put(tokens[1], seqstrings);
345     rstrings.put(tokens[2], new String[]
346     { dsstring });
347     if (idstrings.length != seqstrings.length)
348     {
349       throw new Error(
350               "idstrings and seqstrings contain one string each per sequence.");
351     }
352     return makeUrls(rstrings, onlyIfMatches);
353   }
354
355   public Object[] makeUrls(Hashtable repstrings, boolean onlyIfMatches)
356   {
357     // prepare string arrays in correct order to be assembled into URL input
358     String[][] idseq = new String[mtch.length][]; // indexed by pass
359     int mins = 0, maxs = 0; // allowed two values, 1 or n-sequences.
360     for (int i = 0; i < mtch.length; i++)
361     {
362       idseq[i] = (String[]) repstrings.get(mtch[i]);
363       if (idseq[i].length >= 1)
364       {
365         if (mins == 0 && idseq[i].length == 1)
366         {
367           mins = 1;
368         }
369         if (maxs < 2)
370         {
371           maxs = idseq[i].length;
372         }
373         else
374         {
375           if (maxs != idseq[i].length)
376           {
377             throw new Error(
378                     "Cannot have mixed length replacement vectors. Replacement vector for "
379                             + (mtch[i]) + " is " + idseq[i].length
380                             + " strings long, and have already seen a "
381                             + maxs + " length vector.");
382           }
383         }
384       }
385       else
386       {
387         throw new Error(
388                 "Cannot have zero length vector of replacement strings - either 1 value or n values.");
389       }
390     }
391     int pass = 0;
392     // iterate through input, collating segments to be inserted into url
393     StringBuffer matched[] = new StringBuffer[idseq.length];
394     // and precompile regexes
395     com.stevesoft.pat.Regex[] rgxs = new com.stevesoft.pat.Regex[matched.length];
396     for (pass = 0; pass < matched.length; pass++)
397     {
398       matched[pass] = new StringBuffer();
399       if (regexReplace[pass] != null)
400       {
401         rgxs[pass] = com.stevesoft.pat.Regex.perlCode("/" + regexReplace[pass]
402                 + "/");
403       }
404       else
405       {
406         rgxs[pass] = null;
407       }
408     }
409     // record which of the input sequences were actually used to generate the
410     // url
411     boolean[] thismatched = new boolean[maxs];
412     int seqsmatched = 0;
413     for (int sq = 0; sq < maxs; sq++)
414     {
415       // initialise flag for match
416       thismatched[sq] = false;
417       String[] thematches = new String[rgxs.length];
418       for (pass = 0; pass < rgxs.length; pass++)
419       {
420         thematches[pass] = ""; // initialise - in case there are no more
421         // matches.
422         // if a regex is provided, then it must match for all sequences in all
423         // tokens for it to be considered.
424         if (idseq[pass].length <= sq)
425         {
426           // no more replacement strings to try for this token
427           continue;
428         }
429         if (rgxs[pass] != null)
430         {
431           com.stevesoft.pat.Regex rg = rgxs[pass];
432           int rematchat = 0;
433           // concatenate all matches of re in the given string!
434           while (rg.searchFrom(idseq[pass][sq], rematchat))
435           {
436             rematchat = rg.matchedTo();
437             thismatched[sq] |= true;
438             // do we take the cartesian products of the substituents ?
439             int ns = rg.numSubs();
440             if (ns == 0)
441             {
442               thematches[pass] += rg.stringMatched();// take whole regex
443             }
444             /*
445              * else if (ns==1) { // take only subgroup match return new String[]
446              * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
447              * }; }
448              */
449             // deal with multiple submatch case - for moment we do the simplest
450             // - concatenate the matched regions, instead of creating a complete
451             // list for each alternate match over all sequences.
452             // TODO: specify a 'replace pattern' - next refinement
453             else
454             {
455               // debug
456               for (int s = 0; s <= rg.numSubs(); s++)
457               {
458                 System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
459                         + " : " + rg.matchedTo(s) + " : '"
460                         + rg.stringMatched(s) + "'");
461               }
462               // try to collate subgroup matches
463               StringBuffer subs = new StringBuffer();
464               // have to loop through submatches, collating them at top level
465               // match
466               int s = 0; // 1;
467               while (s <= ns)
468               {
469                 if (s + 1 <= ns && rg.matchedTo(s) > -1
470                         && rg.matchedTo(s + 1) > -1
471                         && rg.matchedTo(s + 1) < rg.matchedTo(s))
472                 {
473                   // s is top level submatch. search for submatches enclosed by
474                   // this one
475                   int r = s + 1;
476                   String rmtch = "";
477                   while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
478                   {
479                     if (rg.matchedFrom(r) > -1)
480                     {
481                       rmtch += rg.stringMatched(r);
482                     }
483                     r++;
484                   }
485                   if (rmtch.length() > 0)
486                   {
487                     subs.append(rmtch); // simply concatenate
488                   }
489                   s = r;
490                 }
491                 else
492                 {
493                   if (rg.matchedFrom(s) > -1)
494                   {
495                     subs.append(rg.stringMatched(s)); // concatenate
496                   }
497                   s++;
498                 }
499               }
500               thematches[pass] += subs.toString();
501             }
502           }
503         }
504         else
505         {
506           // are we only supposed to take regex matches ?
507           if (!onlyIfMatches)
508           {
509             thismatched[sq] |= true;
510             thematches[pass] = idseq[pass][sq]; // take whole string -
511             // regardless - probably not a
512             // good idea!
513             /*
514              * TODO: do some boilerplate trimming of the fields to make them
515              * sensible e.g. trim off any 'prefix' in the id string (see UrlLink
516              * for the below) - pre 2.4 Jalview behaviour if
517              * (idstring.indexOf("|") > -1) { idstring =
518              * idstring.substring(idstring.lastIndexOf("|") + 1); }
519              */
520
521           }
522         }
523       }
524
525       // check if we are going to add this sequence's results ? all token
526       // replacements must be valid for this to happen!
527       // (including single value replacements - eg. dataset name)
528       if (thismatched[sq])
529       {
530         for (pass = 0; pass < matched.length; pass++)
531         {
532           if (idseq[pass].length > 1 && matched[pass].length() > 0)
533           {
534             matched[pass].append(separators[pass]);
535           }
536           matched[pass].append(thematches[pass]);
537         }
538         seqsmatched++;
539       }
540     }
541     // finally, if any sequences matched, then form the URL and return
542     if (matched[0].length() == 0)
543     {
544       // no matches - no url generated
545       return null;
546     }
547     StringBuffer submiturl = new StringBuffer();
548     submiturl.append(url_prefix);
549     for (pass = 0; pass < matched.length; pass++)
550     {
551       submiturl.append(matched[pass]);
552       if (url_suffix[pass] != null)
553       {
554         submiturl.append(url_suffix[pass]);
555       }
556     }
557
558     return new Object[]
559     { new int[]
560     { seqsmatched }, thismatched, matched, new String[]
561     { submiturl.toString() } };
562   }
563
564   /**
565    * get token types present in this url as a bitfield indicating presence of each token from tokens (LSB->MSB).
566    * @return groupURL class as integer
567    */
568   public int getGroupURLType()
569   {
570     int r = 0;
571     for (int pass = 0; pass < tokens.length; pass++)
572     {
573       for (int i = 0; i < mtch.length; i++)
574       {
575         if (mtch[i].equals(tokens[pass]))
576         {
577           r += 1 << pass;
578         }
579       }
580     }
581     return r;
582   }
583
584   public String toString()
585   {
586     StringBuffer result = new StringBuffer();
587     result.append(label + "|" + url_prefix);
588     int r;
589     for (r = 0; r < url_suffix.length; r++)
590     {
591       result.append("$");
592       result.append(mtch[r]);
593       if (regexReplace[r] != null)
594       {
595         result.append("=/");
596         result.append(regexReplace[r]);
597         result.append("/=");
598       }
599       result.append("$");
600       result.append(url_suffix[r]);
601     }
602     for (r = 0; r < separators.length; r++)
603     {
604       result.append("|");
605       result.append(separators[r]);
606     }
607     return result.toString();
608   }
609
610   /**
611    * report stats about the generated url string given an input set
612    * 
613    * @param ul
614    * @param idstring
615    * @param url
616    */
617   private static void testUrls(GroupUrlLink ul, String[][] idstring,
618           Object[] url)
619   {
620
621     if (url == null)
622     {
623       System.out.println("Created NO urls.");
624     }
625     else
626     {
627       System.out.println("Created a url from " + ((int[]) url[0])[0]
628               + "out of " + idstring[0].length + " sequences.");
629       System.out.println("Sequences that did not match:");
630       for (int sq = 0; sq < idstring[0].length; sq++)
631       {
632         if (!((boolean[]) url[1])[sq])
633         {
634           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
635                   + idstring[1][sq]);
636         }
637       }
638       System.out.println("Sequences that DID match:");
639       for (int sq = 0; sq < idstring[0].length; sq++)
640       {
641         if (((boolean[]) url[1])[sq])
642         {
643           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
644                   + idstring[1][sq]);
645         }
646       }
647       System.out.println("The generated URL:");
648       System.out.println(((String[]) url[3])[0]);
649     }
650   }
651
652   public static void main(String argv[])
653   {
654     String[] links = new String[]
655     {
656         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=linkInDatasetFromJalview&input=$SEQUENCEIDS$&inputType=0|,",
657         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=linkInDatasetFromJalview&input=$SEQUENCES$&inputType=1|,",
658         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=$DATASETID$&input=$SEQUENCEIDS$&inputType=0|,",
659         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=$DATASETID$&input=$SEQUENCES$&inputType=1|,",
660         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=$SEQUENCEIDS$&datasetName=linkInDatasetFromJalview&input=$SEQUENCEIDS$&inputType=0|,",
661         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=$SEQUENCEIDS$&datasetName=$DATASETID$&input=$SEQUENCES$&inputType=1|,",
662         "EnVision2 Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Default&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/([a-zA-Z]+)/=$&inputType=1|,",
663         "EnVision2 Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Default&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/[A-Za-z]+/=$&inputType=1|,"
664     /*
665      * http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?input=P38389,P38398
666      * &inputType=0&workflow=Enfin%20Default%20Workflow&datasetName=
667      * linkInDatasetFromPRIDE
668      */
669     };
670
671     SequenceI[] seqs = new SequenceI[]
672     { new Sequence("StupidLabel:gi|9234|pdb|102L|A",
673             "asdiasdpasdpadpwpadasdpaspdw"), };
674     String[][] seqsandids = formStrings(seqs);
675     for (int i = 0; i < links.length; i++)
676     {
677       GroupUrlLink ul = new GroupUrlLink(links[i]);
678       if (ul.isValid())
679       {
680         System.out.println("\n\n\n");
681         System.out.println("Link " + i + " " + links[i] + " : "
682                 + ul.toString());
683         System.out.println(" pref : " + ul.getUrl_prefix());
684         System.out.println(" IdReplace : " + ul.getIDRegexReplace());
685         System.out.println(" SeqReplace : " + ul.getSeqRegexReplace());
686         System.out.println(" Suffixes : " + ul.getUrl_suffix());
687
688         System.out
689                 .println("<insert input id and sequence strings here> Without onlyIfMatches:");
690         Object[] urls = ul.makeUrls(seqsandids[0], seqsandids[1],
691                 "mydataset", false);
692         testUrls(ul, seqsandids, urls);
693         System.out
694                 .println("<insert input id and sequence strings here> With onlyIfMatches set:");
695         urls = ul.makeUrls(seqsandids[0], seqsandids[1], "mydataset", true);
696         testUrls(ul, seqsandids, urls);
697       }
698       else
699       {
700         System.err.println("Invalid URLLink : " + links[i] + " : "
701                 + ul.getInvalidMessage());
702       }
703     }
704   }
705
706   /**
707    * covenience method to generate the id and sequence string vector from a set
708    * of seuqences using each sequence's getName() and getSequenceAsString()
709    * method
710    * 
711    * @param seqs
712    * @return String[][] {{sequence ids},{sequence strings}}
713    */
714   public static String[][] formStrings(SequenceI[] seqs)
715   {
716     String[][] idset = new String[2][seqs.length];
717     for (int i = 0; i < seqs.length; i++)
718     {
719       idset[0][i] = seqs[i].getName();
720       idset[1][i] = seqs[i].getSequenceAsString();
721     }
722     return idset;
723   }
724
725   public void setLabel(String newlabel)
726   {
727     this.label = newlabel;
728   }
729 }