JAL-3253-applet headless branch - just experimenting.
[jalview.git] / src / jalview / bin / JalviewAppLoader.java
1 package jalview.bin;
2
3 import jalview.api.AlignFrameI;
4 import jalview.api.JalviewApp;
5 import jalview.api.StructureSelectionManagerProvider;
6 import jalview.datamodel.Alignment;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.AlignmentOrder;
9 import jalview.datamodel.ColumnSelection;
10 import jalview.datamodel.HiddenColumns;
11 import jalview.datamodel.PDBEntry;
12 import jalview.datamodel.Sequence;
13 import jalview.datamodel.SequenceGroup;
14 import jalview.datamodel.SequenceI;
15 import jalview.gui.AlignFrame;
16 import jalview.gui.AlignViewport;
17 import jalview.gui.Desktop;
18 import jalview.io.AnnotationFile;
19 import jalview.io.AppletFormatAdapter;
20 import jalview.io.DataSourceType;
21 import jalview.io.FeaturesFile;
22 import jalview.io.FileFormat;
23 import jalview.io.FileFormatI;
24 import jalview.io.FileFormats;
25 import jalview.io.IdentifyFile;
26 import jalview.io.JPredFile;
27 import jalview.io.JnetAnnotationMaker;
28 import jalview.io.NewickFile;
29 import jalview.structure.SelectionSource;
30 import jalview.structure.StructureSelectionManager;
31 import jalview.util.HttpUtils;
32 import jalview.util.MessageManager;
33
34 import java.awt.EventQueue;
35 import java.io.IOException;
36 import java.net.URL;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.StringTokenizer;
40 import java.util.Vector;
41
42 /**
43  * A class to load parameters for either JalviewLite or Jalview
44  * 
45  * @author hansonr
46  *
47  */
48 public class JalviewAppLoader
49 {
50
51   private JalviewApp app; // Jalview or JalviewJS or JalviewLite
52
53   private boolean debug;
54
55   String separator = "\u00AC"; // JalviewLite note: the default used to
56                                        // be '|', but many sequence IDS include
57                                        // pipes.
58
59   public String getSeparator()
60   {
61     return separator;
62   }
63
64   public void setSeparator(String separator)
65   {
66     this.separator = separator;
67   }
68
69   public JalviewAppLoader(boolean debug)
70   {
71     this.debug = debug;
72   }
73
74   public void load(JalviewApp app)
75   {
76
77     this.app = app;
78
79     String sep = app.getParameter("separator");
80     if (sep != null)
81     {
82       if (sep.length() > 0)
83       {
84         separator = sep;
85       }
86       else
87       {
88         throw new Error(MessageManager
89                 .getString("error.invalid_separator_parameter"));
90       }
91     }
92
93     loadTree();
94     loadScoreFile();
95     loadFeatures();
96     loadAnnotations();
97     loadJnetFile();
98     loadPdbFiles();
99     callInitCallback();
100   }
101
102   /**
103    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
104    * else false.
105    * 
106    * @param loaderFrame
107    * @return
108    */
109   protected boolean loadPdbFiles()
110   {
111     boolean result = false;
112     /*
113      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
114      * related to JAL-434
115      */
116
117     boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
118     app.setAlignPdbStructures(doAlign);
119     /*
120      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
121      * PDB|1GAQ|1GAQ|C">
122      * 
123      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
124      * 
125      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
126      */
127
128     // Accumulate pdbs here if they are heading for the same view (if
129     // alignPdbStructures is true)
130     Vector<Object[]> pdbs = new Vector<>();
131     // create a lazy matcher if we're asked to
132     jalview.analysis.SequenceIdMatcher matcher = (app
133             .getDefaultParameter("relaxedidmatch", false))
134                     ? new jalview.analysis.SequenceIdMatcher(
135                             app.getViewport().getAlignment()
136                                     .getSequencesArray())
137                     : null;
138
139     int pdbFileCount = 0;
140     String param;
141     do
142     {
143       if (pdbFileCount > 0)
144       {
145         param = app.getParameter("PDBFILE" + pdbFileCount);
146       }
147       else
148       {
149         param = app.getParameter("PDBFILE");
150       }
151
152       if (param != null)
153       {
154         PDBEntry pdb = new PDBEntry();
155
156         String seqstring;
157         SequenceI[] seqs = null;
158         String[] chains = null;
159
160         StringTokenizer st = new StringTokenizer(param, " ");
161
162         if (st.countTokens() < 2)
163         {
164           String sequence = app.getParameter("PDBSEQ");
165           if (sequence != null)
166           {
167             seqs = new SequenceI[] { matcher == null
168                     ? (Sequence) app.getViewport().getAlignment()
169                             .findName(sequence)
170                     : matcher.findIdMatch(sequence) };
171           }
172
173         }
174         else
175         {
176           param = st.nextToken();
177           List<SequenceI> tmp = new ArrayList<>();
178           List<String> tmp2 = new ArrayList<>();
179
180           while (st.hasMoreTokens())
181           {
182             seqstring = st.nextToken();
183             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
184             if (st2.countTokens() > 1)
185             {
186               // This is the chain
187               tmp2.add(st2.nextToken());
188               seqstring = st2.nextToken();
189             }
190             tmp.add(matcher == null
191                     ? (Sequence) app.getViewport().getAlignment()
192                             .findName(seqstring)
193                     : matcher.findIdMatch(seqstring));
194           }
195
196           seqs = tmp.toArray(new SequenceI[tmp.size()]);
197           if (tmp2.size() == tmp.size())
198           {
199             chains = tmp2.toArray(new String[tmp2.size()]);
200           }
201         }
202         pdb.setId(param);
203         ret[0] = param;
204         DataSourceType protocol = resolveFileProtocol(app, ret);
205         // TODO check JAL-357 for files in a jar (CLASSLOADER)
206         pdb.setFile(ret[0]);
207
208         if (seqs != null)
209         {
210           for (int i = 0; i < seqs.length; i++)
211           {
212             if (seqs[i] != null)
213             {
214               ((Sequence) seqs[i]).addPDBId(pdb);
215               StructureSelectionManager
216                       .getStructureSelectionManager(
217                               (StructureSelectionManagerProvider) app)
218                       .registerPDBEntry(pdb);
219             }
220             else
221             {
222               if (debug)
223               {
224                 // this may not really be a problem but we give a warning
225                 // anyway
226                 System.err.println(
227                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
228                                 + i + ")");
229               }
230             }
231           }
232
233           if (doAlign)
234           {
235             pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
236           }
237           else
238           {
239             app.newStructureView(pdb, seqs, chains, protocol);
240           }
241         }
242       }
243
244       pdbFileCount++;
245     } while (param != null || pdbFileCount < 10);
246     if (pdbs.size() > 0)
247     {
248       SequenceI[][] seqs = new SequenceI[pdbs.size()][];
249       PDBEntry[] pdb = new PDBEntry[pdbs.size()];
250       String[][] chains = new String[pdbs.size()][];
251       String[] protocols = new String[pdbs.size()];
252       for (int pdbsi = 0, pdbsiSize = pdbs
253               .size(); pdbsi < pdbsiSize; pdbsi++)
254       {
255         Object[] o = pdbs.elementAt(pdbsi);
256         pdb[pdbsi] = (PDBEntry) o[0];
257         seqs[pdbsi] = (SequenceI[]) o[1];
258         chains[pdbsi] = (String[]) o[2];
259         protocols[pdbsi] = (String) o[3];
260       }
261       app.alignedStructureView(pdb, seqs, chains, protocols);
262       result = true;
263     }
264     return result;
265   }
266
267   /**
268    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
269    * false.
270    * 
271    * @param alignFrame
272    * @return
273    */
274   protected boolean loadJnetFile()
275   {
276     boolean result = false;
277     String param = app.getParameter("jnetfile");
278     if (param == null)
279     {
280       // jnet became jpred around 2016
281       param = app.getParameter("jpredfile");
282     }
283     if (param != null)
284     {
285       try
286       {
287         ret[0] = param;
288         DataSourceType protocol = resolveFileProtocol(app, ret);
289         JPredFile predictions = new JPredFile(ret[0], protocol);
290         JnetAnnotationMaker.add_annotation(predictions,
291                 app.getViewport().getAlignment(), 0, false);
292         // false == do not add sequence profile from concise output
293         app.getViewport().getAlignment().setupJPredAlignment();
294         app.updateForAnnotations();
295         result = true;
296       } catch (Exception ex)
297       {
298         ex.printStackTrace();
299       }
300     }
301     return result;
302   }
303
304   /**
305    * Load annotations if specified by parameter. Returns true if loaded, else
306    * false.
307    * 
308    * @param alignFrame
309    * @return
310    */
311   protected boolean loadAnnotations()
312   {
313     boolean result = false;
314     String param = app.getParameter("annotations");
315     if (param != null)
316     {
317       ret[0] = param;
318       DataSourceType protocol = resolveFileProtocol(app, ret);
319       param = ret[0];
320       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
321               param, protocol))
322       {
323         app.updateForAnnotations();
324         result = true;
325       }
326       else
327       {
328         System.err
329                 .println("Annotations were not added from annotation file '"
330                         + param + "'");
331       }
332     }
333     return result;
334   }
335
336   /**
337    * Load features file and view settings as specified by parameters. Returns
338    * true if features were loaded, else false.
339    * 
340    * @param alignFrame
341    * @return
342    */
343   protected boolean loadFeatures()
344   {
345     boolean result = false;
346     // ///////////////////////////
347     // modify display of features
348     // we do this before any features have been loaded, ensuring any hidden
349     // groups are hidden when features first displayed
350     //
351     // hide specific groups
352     //
353     String param = app.getParameter("hidefeaturegroups");
354     if (param != null)
355     {
356       app.setFeatureGroupState(separatorListToArray(param, separator),
357               false);
358       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
359     }
360     // show specific groups
361     param = app.getParameter("showfeaturegroups");
362     if (param != null)
363     {
364       app.setFeatureGroupState(separatorListToArray(param, separator),
365               true);
366       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
367     }
368     // and now load features
369     param = app.getParameter("features");
370     if (param != null)
371     {
372       ret[0] = param;
373       DataSourceType protocol = resolveFileProtocol(app, ret);
374
375       result = app.parseFeaturesFile(ret[0], protocol);
376     }
377
378     param = app.getParameter("showFeatureSettings");
379     if (param != null && param.equalsIgnoreCase("true"))
380     {
381       app.newFeatureSettings();
382     }
383     return result;
384   }
385
386   /**
387    * Load a score file if specified by parameter. Returns true if file was
388    * loaded, else false.
389    * 
390    * @param loaderFrame
391    */
392   protected boolean loadScoreFile()
393   {
394     boolean result = false;
395     String sScoreFile = app.getParameter("scoreFile");
396     if (sScoreFile != null && !"".equals(sScoreFile))
397     {
398       try
399       {
400         if (debug)
401         {
402           System.err.println(
403                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
404         }
405         result = app.loadScoreFile(sScoreFile);
406         if (!result)
407         {
408           System.err.println(
409                   "Failed to parse T-COFFEE parameter as a valid score file ('"
410                           + sScoreFile + "')");
411         }
412       } catch (Exception e)
413       {
414         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
415                 sScoreFile, e.getMessage());
416       }
417     }
418     return result;
419   }
420
421   String[] ret = new String[1];
422
423   /**
424    * Load a tree for the alignment if specified by parameter. Returns true if a
425    * tree was loaded, else false.
426    * 
427    * @param loaderFrame
428    * @return
429    */
430   protected boolean loadTree()
431   {
432     boolean result = false;
433     String treeFile = app.getParameter("tree");
434     if (treeFile == null)
435     {
436       treeFile = app.getParameter("treeFile");
437     }
438
439     if (treeFile != null)
440     {
441       try
442       {
443         ret[0] = treeFile;
444         NewickFile fin = new NewickFile(treeFile,
445                 resolveFileProtocol(app, ret));
446         fin.parse();
447
448         if (fin.getTree() != null)
449         {
450           app.loadTree(fin, ret[0]);
451           result = true;
452           if (debug)
453           {
454             System.out.println("Successfully imported tree.");
455           }
456         }
457         else
458         {
459           if (debug)
460           {
461             System.out.println(
462                     "Tree parameter did not resolve to a valid tree.");
463           }
464         }
465       } catch (Exception ex)
466       {
467         ex.printStackTrace();
468       }
469     }
470     return result;
471   }
472
473   /**
474    * form a complete URL given a path to a resource and a reference location on
475    * the same server
476    * 
477    * @param targetPath
478    *          - an absolute path on the same server as localref or a document
479    *          located relative to localref
480    * @param localref
481    *          - a URL on the same server as url
482    * @return a complete URL for the resource located by url
483    */
484   public static String resolveUrlForLocalOrAbsolute(String targetPath,
485           URL localref)
486   {
487     String resolvedPath = "";
488     if (targetPath.startsWith("/"))
489     {
490       String codebase = localref.toString();
491       String localfile = localref.getFile();
492       resolvedPath = codebase.substring(0,
493               codebase.length() - localfile.length()) + targetPath;
494       return resolvedPath;
495     }
496
497     /*
498      * get URL path and strip off any trailing file e.g.
499      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
500      * www.jalview.org/examples/
501      */
502     String urlPath = localref.toString();
503     String directoryPath = urlPath;
504     int lastSeparator = directoryPath.lastIndexOf("/");
505     if (lastSeparator > 0)
506     {
507       directoryPath = directoryPath.substring(0, lastSeparator + 1);
508     }
509
510     if (targetPath.startsWith("/"))
511     {
512       /*
513        * construct absolute URL to a file on the server - this is not allowed?
514        */
515       // String localfile = localref.getFile();
516       // resolvedPath = urlPath.substring(0,
517       // urlPath.length() - localfile.length())
518       // + targetPath;
519       resolvedPath = directoryPath + targetPath.substring(1);
520     }
521     else
522     {
523       resolvedPath = directoryPath + targetPath;
524     }
525     // if (debug)
526     // {
527     // System.err.println(
528     // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
529     // }
530     return resolvedPath;
531   }
532
533   /**
534    * parse the string into a list
535    * 
536    * @param list
537    * @param separator
538    * @return elements separated by separator
539    */
540   public static String[] separatorListToArray(String list, String separator)
541   {
542     // TODO use StringUtils version (slightly different...)
543     int seplen = separator.length();
544     if (list == null || list.equals("") || list.equals(separator))
545     {
546       return null;
547     }
548     Vector<String> jv = new Vector<>();
549     int cp = 0, pos;
550     while ((pos = list.indexOf(separator, cp)) > cp)
551     {
552       jv.addElement(list.substring(cp, pos));
553       cp = pos + seplen;
554     }
555     if (cp < list.length())
556     {
557       String c = list.substring(cp);
558       if (!c.equals(separator))
559       {
560         jv.addElement(c);
561       }
562     }
563     if (jv.size() > 0)
564     {
565       String[] v = new String[jv.size()];
566       for (int i = 0; i < v.length; i++)
567       {
568         v[i] = jv.elementAt(i);
569       }
570       jv.removeAllElements();
571       // if (debug)
572       // {
573       // System.err.println("Array from '" + separator
574       // + "' separated List:\n" + v.length);
575       // for (int i = 0; i < v.length; i++)
576       // {
577       // System.err.println("item " + i + " '" + v[i] + "'");
578       // }
579       // }
580       return v;
581     }
582     // if (debug)
583     // {
584     // System.err.println(
585     // "Empty Array from '" + separator + "' separated List");
586     // }
587     return null;
588   }
589
590   public static DataSourceType resolveFileProtocol(JalviewApp app,
591           String[] retPath)
592   {
593     String path = retPath[0];
594     /*
595      * is it paste data?
596      */
597     if (path.startsWith("PASTE"))
598     {
599       retPath[0] = path.substring(5);
600       return DataSourceType.PASTE;
601     }
602
603     /*
604      * is it a URL?
605      */
606     if (path.indexOf("://") >= 0)
607     {
608       return DataSourceType.URL;
609     }
610
611     /*
612      * try relative to document root
613      */
614     URL documentBase = app.getDocumentBase();
615     String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
616     if (HttpUtils.isValidUrl(withDocBase))
617     {
618       // if (debug)
619       // {
620       // System.err.println("Prepended document base '" + documentBase
621       // + "' to make: '" + withDocBase + "'");
622       // }
623       retPath[0] = withDocBase;
624       return DataSourceType.URL;
625     }
626
627     /*
628      * try relative to codebase (if different to document base)
629      */
630     URL codeBase = app.getCodeBase();
631     String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
632     if (!withCodeBase.equals(withDocBase)
633             && HttpUtils.isValidUrl(withCodeBase))
634     {
635       // if (debug)
636       // {
637       // System.err.println("Prepended codebase '" + codeBase
638       // + "' to make: '" + withCodeBase + "'");
639       // }
640       retPath[0] = withCodeBase;
641       return DataSourceType.URL;
642     }
643
644     /*
645      * try locating by classloader; try this last so files in the directory
646      * are resolved using document base
647      */
648     if (inArchive(app.getClass(), path))
649     {
650       return DataSourceType.CLASSLOADER;
651     }
652     return null;
653   }
654
655   /**
656    * Discovers whether the given file is in the Applet Archive
657    * 
658    * @param f
659    *          String
660    * @return boolean
661    */
662   private static boolean inArchive(Class<?> c, String f)
663   {
664     // This might throw a security exception in certain browsers
665     // Netscape Communicator for instance.
666     try
667     {
668       boolean rtn = (c.getResourceAsStream("/" + f) != null);
669       // if (debug)
670       // {
671       // System.err.println("Resource '" + f + "' was "
672       // + (rtn ? "" : "not ") + "located by classloader.");
673       // }
674       return rtn;
675     } catch (Exception ex)
676     {
677       System.out.println("Exception checking resources: " + f + " " + ex);
678       return false;
679     }
680   }
681
682   public void callInitCallback()
683   {
684     String initjscallback = app.getParameter("oninit");
685     if (initjscallback == null)
686     {
687       return;
688     }
689     initjscallback = initjscallback.trim();
690     if (initjscallback.length() > 0)
691     {
692       // TODO
693     }
694   }
695
696   /**
697    * read sequence1...sequenceN as a raw alignment
698    * 
699    * @param jalviewApp
700    * @return
701    */
702   public String getPastedSequence(JalviewApp jalviewApp)
703   {
704     StringBuffer data = new StringBuffer("PASTE");
705     int i = 1;
706     String file = null;
707     while ((file = app.getParameter("sequence" + i)) != null)
708     {
709       data.append(file.toString() + "\n");
710       i++;
711     }
712     if (data.length() > 5)
713     {
714       file = data.toString();
715     }
716     return file;
717   }
718
719   /**
720    * concatenate the list with separator
721    * 
722    * @param list
723    * @param separator
724    * @return concatenated string
725    */
726   public static String arrayToSeparatorList(String[] list, String separator)
727   {
728     // TODO use StringUtils version
729     StringBuffer v = new StringBuffer();
730     if (list != null && list.length > 0)
731     {
732       for (int i = 0, iSize = list.length; i < iSize; i++)
733       {
734         if (list[i] != null)
735         {
736           if (i > 0)
737           {
738             v.append(separator);
739           }
740           v.append(list[i]);
741         }
742       }
743       // if (debug)
744       // {
745       // System.err
746       // .println("Returning '" + separator + "' separated List:\n");
747       // System.err.println(v);
748       // }
749       return v.toString();
750     }
751     // if (debug)
752     // {
753     // System.err.println(
754     // "Returning empty '" + separator + "' separated List\n");
755     // }
756     return "" + separator;
757   }
758
759   public String arrayToSeparatorList(String[] array)
760   {
761     return arrayToSeparatorList(array, separator);
762   }
763
764   public String getSelectedSequencesFrom(AlignFrameI alf, String sep)
765   {
766     StringBuffer result = new StringBuffer("");
767     if (sep == null || sep.length() == 0)
768     {
769       sep = separator; // "+0x00AC;
770     }
771     AlignViewport v = ((AlignFrame) alf).getViewport();
772     if (v.getSelectionGroup() != null)
773     {
774       SequenceI[] seqs = v.getSelectionGroup()
775               .getSequencesInOrder(v.getAlignment());
776
777       for (int i = 0; i < seqs.length; i++)
778       {
779         result.append(seqs[i].getName());
780         result.append(sep);
781       }
782     }
783
784     return result.toString();
785   }
786
787   public void setFeatureGroupStateOn(final AlignFrameI alf,
788           final String groups, boolean state)
789   {
790     java.awt.EventQueue.invokeLater(new Runnable()
791     {
792       @Override
793       public void run()
794       {
795         ((AlignFrame) alf).setFeatureGroupState(
796                 separatorListToArray(groups, separator), state);
797       }
798     });
799   }
800
801   public String getFeatureGroupsOfStateOn(AlignFrameI alf, boolean visible)
802   {
803     return arrayToSeparatorList(
804             ((AlignFrame) alf).getFeatureGroupsOfState(visible));
805   }
806
807   public void scrollViewToIn(final AlignFrameI alf, final String topRow,
808           final String leftHandColumn)
809   {
810     java.awt.EventQueue.invokeLater(new Runnable()
811     {
812       @Override
813       public void run()
814       {
815         try
816         {
817           ((AlignFrame) alf).scrollTo(new Integer(topRow).intValue(),
818                   new Integer(leftHandColumn).intValue());
819
820         } catch (Exception ex)
821         {
822           System.err.println("Couldn't parse integer arguments (topRow='"
823                   + topRow + "' and leftHandColumn='" + leftHandColumn
824                   + "')");
825           ex.printStackTrace();
826         }
827       }
828     });
829   }
830
831   public void scrollViewToRowIn(final AlignFrameI alf, final String topRow)
832   {
833
834     java.awt.EventQueue.invokeLater(new Runnable()
835     {
836       @Override
837       public void run()
838       {
839         try
840         {
841           ((AlignFrame) alf).scrollToRow(new Integer(topRow).intValue());
842
843         } catch (Exception ex)
844         {
845           System.err.println("Couldn't parse integer arguments (topRow='"
846                   + topRow + "')");
847           ex.printStackTrace();
848         }
849
850       }
851     });
852   }
853
854   public void scrollViewToColumnIn(final AlignFrameI alf,
855           final String leftHandColumn)
856   {
857     java.awt.EventQueue.invokeLater(new Runnable()
858     {
859
860       @Override
861       public void run()
862       {
863         try
864         {
865           ((AlignFrame) alf)
866                   .scrollToColumn(new Integer(leftHandColumn).intValue());
867
868         } catch (Exception ex)
869         {
870           System.err.println(
871                   "Couldn't parse integer arguments (leftHandColumn='"
872                           + leftHandColumn + "')");
873           ex.printStackTrace();
874         }
875       }
876     });
877
878   }
879
880   public boolean addPdbFile(AlignFrameI alf, String sequenceId,
881           String pdbEntryString, String pdbFile)
882   {
883     AlignFrame alFrame = (AlignFrame) alf;
884     SequenceI toaddpdb = alFrame.getViewport().getAlignment()
885             .findName(sequenceId);
886     boolean needtoadd = false;
887     if (toaddpdb != null)
888     {
889       Vector<PDBEntry> pdbe = toaddpdb.getAllPDBEntries();
890       PDBEntry pdbentry = null;
891       if (pdbe != null && pdbe.size() > 0)
892       {
893         for (int pe = 0, peSize = pdbe.size(); pe < peSize; pe++)
894         {
895           pdbentry = pdbe.elementAt(pe);
896           if (!pdbentry.getId().equals(pdbEntryString)
897                   && !pdbentry.getFile().equals(pdbFile))
898           {
899             pdbentry = null;
900           }
901           else
902           {
903             continue;
904           }
905         }
906       }
907       if (pdbentry == null)
908       {
909         pdbentry = new PDBEntry();
910         pdbentry.setId(pdbEntryString);
911         pdbentry.setFile(pdbFile);
912         needtoadd = true; // add this new entry to sequence.
913       }
914       // resolve data source
915       // TODO: this code should be a refactored to an io package
916       DataSourceType protocol = AppletFormatAdapter.resolveProtocol(pdbFile,
917               FileFormat.PDB);
918       if (protocol == null)
919       {
920         return false;
921       }
922       if (needtoadd)
923       {
924         pdbentry.setProperty("protocol", protocol);
925         toaddpdb.addPDBId(pdbentry);
926         alFrame.alignPanel.getStructureSelectionManager()
927                 .registerPDBEntry(pdbentry);
928       }
929     }
930     return true;
931   }
932
933   public AlignFrameI loadAlignment(String text, int width, int height,
934           String title)
935   {
936     AlignmentI al = null;
937
938     try
939     {
940       FileFormatI format = new IdentifyFile().identify(text,
941               DataSourceType.PASTE);
942       al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
943               format);
944       if (al.getHeight() > 0)
945       {
946         return new AlignFrame(al, width, height, title);
947       }
948     } catch (IOException ex)
949     {
950       ex.printStackTrace();
951     }
952     return null;
953   }
954
955   public String getFeatureGroupsOn(AlignFrameI alf)
956   {
957     return arrayToSeparatorList(
958             ((AlignFrame) alf).getFeatureGroups());
959   }
960
961   public void highlightIn(final AlignFrameI alf, final String sequenceId,
962           final String position, final String alignedPosition)
963   {
964     // TODO: could try to highlight in all alignments if alf==null
965     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
966             ((AlignFrame) alf).getViewport().getAlignment()
967                     .getSequencesArray());
968     final SequenceI sq = matcher.findIdMatch(sequenceId);
969     if (sq != null)
970     {
971       int apos = -1;
972       try
973       {
974         apos = new Integer(position).intValue();
975         apos--;
976       } catch (NumberFormatException ex)
977       {
978         return;
979       }
980       final int pos = apos;
981       // use vamsas listener to broadcast to all listeners in scope
982       if (alignedPosition != null && (alignedPosition.trim().length() == 0
983               || alignedPosition.toLowerCase().indexOf("false") > -1))
984       {
985         java.awt.EventQueue.invokeLater(new Runnable()
986         {
987           @Override
988           public void run()
989           {
990             if (Desktop.getInstance() != null)
991             {
992               StructureSelectionManager
993                     .getStructureSelectionManager(Desktop.getInstance())
994                     .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
995             }
996           }
997         });
998       }
999       else
1000       {
1001         java.awt.EventQueue.invokeLater(new Runnable()
1002         {
1003           @Override
1004           public void run()
1005           {
1006             StructureSelectionManager
1007                     .getStructureSelectionManager(Desktop.getInstance())
1008                     .mouseOverVamsasSequence(sq, pos, null);
1009           }
1010         });
1011       }
1012     }
1013   }
1014
1015   public void selectIn(final AlignFrameI alf, String sequenceIds,
1016           String columns, String sep)
1017   {
1018     if (sep == null || sep.length() == 0)
1019     {
1020       sep = separator;
1021     }
1022     else
1023     {
1024       if (debug)
1025       {
1026         System.err.println("Selecting region using separator string '"
1027                 + separator + "'");
1028       }
1029     }
1030     // deparse fields
1031     String[] ids = JalviewAppLoader.separatorListToArray(sequenceIds, sep);
1032     String[] cols = JalviewAppLoader.separatorListToArray(columns, sep);
1033     final SequenceGroup sel = new SequenceGroup();
1034     final ColumnSelection csel = new ColumnSelection();
1035     AlignmentI al = ((AlignFrame) alf).getViewport().getAlignment();
1036     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1037             ((AlignFrame) alf).getViewport().getAlignment()
1038                     .getSequencesArray());
1039     int start = 0, end = al.getWidth(), alw = al.getWidth();
1040     boolean seqsfound = true;
1041     if (ids != null && ids.length > 0)
1042     {
1043       seqsfound = false;
1044       for (int i = 0; i < ids.length; i++)
1045       {
1046         if (ids[i].trim().length() == 0)
1047         {
1048           continue;
1049         }
1050         SequenceI sq = matcher.findIdMatch(ids[i]);
1051         if (sq != null)
1052         {
1053           seqsfound = true;
1054           sel.addSequence(sq, false);
1055         }
1056       }
1057     }
1058     boolean inseqpos = false;
1059     if (cols != null && cols.length > 0)
1060     {
1061       boolean seset = false;
1062       for (int i = 0; i < cols.length; i++)
1063       {
1064         String cl = cols[i].trim();
1065         if (cl.length() == 0)
1066         {
1067           continue;
1068         }
1069         int p;
1070         if ((p = cl.indexOf("-")) > -1)
1071         {
1072           int from = -1, to = -1;
1073           try
1074           {
1075             from = new Integer(cl.substring(0, p)).intValue();
1076             from--;
1077           } catch (NumberFormatException ex)
1078           {
1079             System.err.println(
1080                     "ERROR: Couldn't parse first integer in range element column selection string '"
1081                             + cl + "' - format is 'from-to'");
1082             return;
1083           }
1084           try
1085           {
1086             to = new Integer(cl.substring(p + 1)).intValue();
1087             to--;
1088           } catch (NumberFormatException ex)
1089           {
1090             System.err.println(
1091                     "ERROR: Couldn't parse second integer in range element column selection string '"
1092                             + cl + "' - format is 'from-to'");
1093             return;
1094           }
1095           if (from >= 0 && to >= 0)
1096           {
1097             // valid range
1098             if (from < to)
1099             {
1100               int t = to;
1101               to = from;
1102               to = t;
1103             }
1104             if (!seset)
1105             {
1106               start = from;
1107               end = to;
1108               seset = true;
1109             }
1110             else
1111             {
1112               // comment to prevent range extension
1113               if (start > from)
1114               {
1115                 start = from;
1116               }
1117               if (end < to)
1118               {
1119                 end = to;
1120               }
1121             }
1122             for (int r = from; r <= to; r++)
1123             {
1124               if (r >= 0 && r < alw)
1125               {
1126                 csel.addElement(r);
1127               }
1128             }
1129             if (debug)
1130             {
1131               System.err.println("Range '" + cl + "' deparsed as [" + from
1132                       + "," + to + "]");
1133             }
1134           }
1135           else
1136           {
1137             System.err.println("ERROR: Invalid Range '" + cl
1138                     + "' deparsed as [" + from + "," + to + "]");
1139           }
1140         }
1141         else
1142         {
1143           int r = -1;
1144           try
1145           {
1146             r = new Integer(cl).intValue();
1147             r--;
1148           } catch (NumberFormatException ex)
1149           {
1150             if (cl.toLowerCase().equals("sequence"))
1151             {
1152               // we are in the dataset sequence's coordinate frame.
1153               inseqpos = true;
1154             }
1155             else
1156             {
1157               System.err.println(
1158                       "ERROR: Couldn't parse integer from point selection element of column selection string '"
1159                               + cl + "'");
1160               return;
1161             }
1162           }
1163           if (r >= 0 && r <= alw)
1164           {
1165             if (!seset)
1166             {
1167               start = r;
1168               end = r;
1169               seset = true;
1170             }
1171             else
1172             {
1173               // comment to prevent range extension
1174               if (start > r)
1175               {
1176                 start = r;
1177               }
1178               if (end < r)
1179               {
1180                 end = r;
1181               }
1182             }
1183             csel.addElement(r);
1184             if (debug)
1185             {
1186               System.err.println("Point selection '" + cl
1187                       + "' deparsed as [" + r + "]");
1188             }
1189           }
1190           else
1191           {
1192             System.err.println("ERROR: Invalid Point selection '" + cl
1193                     + "' deparsed as [" + r + "]");
1194           }
1195         }
1196       }
1197     }
1198     if (seqsfound)
1199     {
1200       // we only propagate the selection when it was the null selection, or the
1201       // given sequences were found in the alignment.
1202       if (inseqpos && sel.getSize() > 0)
1203       {
1204         // assume first sequence provides reference frame ?
1205         SequenceI rs = sel.getSequenceAt(0);
1206         start = rs.findIndex(start);
1207         end = rs.findIndex(end);
1208         List<Integer> cs = new ArrayList<>(csel.getSelected());
1209         csel.clear();
1210         for (Integer selectedCol : cs)
1211         {
1212           csel.addElement(rs.findIndex(selectedCol));
1213         }
1214       }
1215       sel.setStartRes(start);
1216       sel.setEndRes(end);
1217       EventQueue.invokeLater(new Runnable()
1218       {
1219         @Override
1220         public void run()
1221         {
1222           ((AlignFrame) alf).select(sel, csel, ((AlignFrame) alf)
1223                   .getCurrentView().getAlignment().getHiddenColumns());
1224         }
1225       });
1226     }
1227   }
1228
1229   public String getAlignmentOrderFrom(AlignFrameI alf, String sep)
1230   {
1231     AlignmentI alorder = ((AlignFrame) alf).getViewport().getAlignment();
1232     String[] order = new String[alorder.getHeight()];
1233     for (int i = 0; i < order.length; i++)
1234     {
1235       order[i] = alorder.getSequenceAt(i).getName();
1236     }
1237     return arrayToSeparatorList(order, sep);
1238   }
1239
1240   public String getSelectedSequencesAsAlignmentFrom(AlignFrameI alf,
1241           String format, String suffix)
1242   {
1243     try
1244     {
1245       AlignViewport vp = ((AlignFrame) alf).getViewport();
1246       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1247       boolean seqlimits = (suffix == null
1248               || suffix.equalsIgnoreCase("true"));
1249       if (vp.getSelectionGroup() != null)
1250       {
1251         // JBPNote: getSelectionAsNewSequence behaviour has changed - this
1252         // method now returns a full copy of sequence data
1253         // TODO consider using getSequenceSelection instead here
1254         String reply = new AppletFormatAdapter().formatSequences(theFormat,
1255                 new Alignment(vp.getSelectionAsNewSequence()),
1256                 seqlimits);
1257         return reply;
1258       }
1259     } catch (IllegalArgumentException ex)
1260     {
1261       ex.printStackTrace();
1262       return "Error retrieving alignment, possibly invalid format specifier: "
1263               + format;
1264     }
1265     return "";
1266   }
1267
1268   public String orderAlignmentBy(AlignFrameI alf, String order,
1269           String undoName, String sep)
1270   {
1271     if (sep == null || sep.length() == 0)
1272     {
1273       sep = separator;
1274     }
1275     String[] ids = JalviewAppLoader.separatorListToArray(order, sep);
1276     SequenceI[] sqs = null;
1277     if (ids != null && ids.length > 0)
1278     {
1279       jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1280               ((AlignFrame) alf).getViewport().getAlignment()
1281                       .getSequencesArray());
1282       int s = 0;
1283       sqs = new SequenceI[ids.length];
1284       for (int i = 0; i < ids.length; i++)
1285       {
1286         if (ids[i].trim().length() == 0)
1287         {
1288           continue;
1289         }
1290         SequenceI sq = matcher.findIdMatch(ids[i]);
1291         if (sq != null)
1292         {
1293           sqs[s++] = sq;
1294         }
1295       }
1296       if (s > 0)
1297       {
1298         SequenceI[] sqq = new SequenceI[s];
1299         System.arraycopy(sqs, 0, sqq, 0, s);
1300         sqs = sqq;
1301       }
1302       else
1303       {
1304         sqs = null;
1305       }
1306     }
1307     if (sqs == null)
1308     {
1309       return "";
1310     }
1311     ;
1312     final AlignmentOrder aorder = new AlignmentOrder(sqs);
1313
1314     if (undoName != null && undoName.trim().length() == 0)
1315     {
1316       undoName = null;
1317     }
1318     final String _undoName = undoName;
1319     // TODO: deal with synchronization here: cannot raise any events until after
1320     // this has returned.
1321     return ((AlignFrame) alf).sortBy(aorder, _undoName) ? "true" : "";
1322   }
1323
1324   public String getAlignmentFrom(AlignFrameI alf, String format,
1325           String suffix)
1326   {
1327     try
1328     {
1329       boolean seqlimits = (suffix == null
1330               || suffix.equalsIgnoreCase("true"));
1331
1332       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1333       String reply = new AppletFormatAdapter().formatSequences(theFormat,
1334               ((AlignFrame) alf).getViewport().getAlignment(), seqlimits);
1335       return reply;
1336     } catch (IllegalArgumentException ex)
1337     {
1338       ex.printStackTrace();
1339       return "Error retrieving alignment, possibly invalid format specifier: "
1340               + format;
1341     }
1342   }
1343
1344   public void loadAnnotationFrom(AlignFrameI alf, String annotation)
1345   {
1346     if (new AnnotationFile().annotateAlignmentView(
1347             ((AlignFrame) alf).getViewport(), annotation,
1348             DataSourceType.PASTE))
1349     {
1350       ((AlignFrame) alf).alignPanel.fontChanged();
1351       ((AlignFrame) alf).alignPanel.setScrollValues(0, 0);
1352     }
1353     else
1354     {
1355       ((AlignFrame) alf).parseFeaturesFile(annotation,
1356               DataSourceType.PASTE);
1357     }
1358   }
1359
1360   public boolean loadFeaturesFrom(AlignFrameI alf, String features,
1361           boolean autoenabledisplay)
1362   {
1363     boolean ret = ((AlignFrame) alf).parseFeaturesFile(features,
1364             DataSourceType.PASTE);
1365     if (!ret)
1366     {
1367       return false;
1368     }
1369     if (autoenabledisplay)
1370     {
1371       ((AlignFrame) alf).getViewport().setShowSequenceFeatures(true);
1372       // this next was for a checkbox in JalviewLite
1373       // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
1374     }
1375     return true;
1376   }
1377
1378   public String getFeaturesFrom(AlignFrameI alf, String format)
1379   {
1380     AlignFrame f = ((AlignFrame) alf);
1381
1382     String features;
1383     FeaturesFile formatter = new FeaturesFile();
1384     if (format.equalsIgnoreCase("Jalview"))
1385     {
1386       features = formatter.printJalviewFormat(
1387               f.getViewport().getAlignment().getSequencesArray(),
1388               f.alignPanel.getFeatureRenderer(), true);
1389     }
1390     else
1391     {
1392       features = formatter.printGffFormat(
1393               f.getViewport().getAlignment().getSequencesArray(),
1394               f.alignPanel.getFeatureRenderer(), true);
1395     }
1396
1397     if (features == null)
1398     {
1399       features = "";
1400     }
1401     return features;
1402
1403   }
1404
1405   public String getAnnotationFrom(AlignFrameI alf)
1406   {
1407     AlignFrame f = (AlignFrame) alf;
1408     String annotation = new AnnotationFile()
1409             .printAnnotationsForView(f.getViewport());
1410     return annotation;
1411   }
1412
1413   public AlignFrameI newViewFrom(AlignFrameI alf, String name)
1414   {
1415     return (AlignFrameI) ((AlignFrame) alf).newView(name, true);
1416   }
1417
1418   public String[] separatorListToArray(String list)
1419   {
1420     return separatorListToArray(list, separator);
1421   }
1422
1423   public Object[] getSelectionForListener(AlignFrameI currentFrame,
1424           SequenceGroup seqsel, ColumnSelection colsel,
1425           HiddenColumns hidden, SelectionSource source, Object alignFrame)
1426   {
1427     // System.err.println("Testing selection event relay to
1428     // jsfunction:"+_listener);
1429     String setid = "";
1430     AlignFrame src = (AlignFrame) alignFrame;
1431     if (source != null)
1432     {
1433       if (source instanceof AlignViewport
1434               && ((AlignFrame) currentFrame).getViewport() == source)
1435       {
1436         // should be valid if it just generated an event!
1437         src = (AlignFrame) currentFrame;
1438
1439       }
1440     }
1441     String[] seqs = new String[] {};
1442     String[] cols = new String[] {};
1443     int strt = 0, end = (src == null) ? -1
1444             : src.alignPanel.av.getAlignment().getWidth();
1445     if (seqsel != null && seqsel.getSize() > 0)
1446     {
1447       seqs = new String[seqsel.getSize()];
1448       for (int i = 0; i < seqs.length; i++)
1449       {
1450         seqs[i] = seqsel.getSequenceAt(i).getName();
1451       }
1452       if (strt < seqsel.getStartRes())
1453       {
1454         strt = seqsel.getStartRes();
1455       }
1456       if (end == -1 || end > seqsel.getEndRes())
1457       {
1458         end = seqsel.getEndRes();
1459       }
1460     }
1461     if (colsel != null && !colsel.isEmpty())
1462     {
1463       if (end == -1)
1464       {
1465         end = colsel.getMax() + 1;
1466       }
1467       cols = new String[colsel.getSelected().size()];
1468       for (int i = 0; i < cols.length; i++)
1469       {
1470         cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
1471       }
1472     }
1473     else
1474     {
1475       if (seqsel != null && seqsel.getSize() > 0)
1476       {
1477         // send a valid range, otherwise we send the empty selection
1478         cols = new String[2];
1479         cols[0] = "" + (1 + strt) + "-" + (1 + end);
1480       }
1481     }
1482     return new Object[] { src, setid, arrayToSeparatorList(seqs),
1483         arrayToSeparatorList(cols) };
1484   }
1485
1486 }