Merge branch 'Jalview-JS/jim/JAL-3253-JAL-3418' into Jalview-JS/JAL-3253-applet
[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             StructureSelectionManager
991                     .getStructureSelectionManager(Desktop.getInstance())
992                     .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
993           }
994         });
995       }
996       else
997       {
998         java.awt.EventQueue.invokeLater(new Runnable()
999         {
1000           @Override
1001           public void run()
1002           {
1003             StructureSelectionManager
1004                     .getStructureSelectionManager(Desktop.getInstance())
1005                     .mouseOverVamsasSequence(sq, pos, null);
1006           }
1007         });
1008       }
1009     }
1010   }
1011
1012   public void selectIn(final AlignFrameI alf, String sequenceIds,
1013           String columns, String sep)
1014   {
1015     if (sep == null || sep.length() == 0)
1016     {
1017       sep = separator;
1018     }
1019     else
1020     {
1021       if (debug)
1022       {
1023         System.err.println("Selecting region using separator string '"
1024                 + separator + "'");
1025       }
1026     }
1027     // deparse fields
1028     String[] ids = JalviewAppLoader.separatorListToArray(sequenceIds, sep);
1029     String[] cols = JalviewAppLoader.separatorListToArray(columns, sep);
1030     final SequenceGroup sel = new SequenceGroup();
1031     final ColumnSelection csel = new ColumnSelection();
1032     AlignmentI al = ((AlignFrame) alf).getViewport().getAlignment();
1033     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1034             ((AlignFrame) alf).getViewport().getAlignment()
1035                     .getSequencesArray());
1036     int start = 0, end = al.getWidth(), alw = al.getWidth();
1037     boolean seqsfound = true;
1038     if (ids != null && ids.length > 0)
1039     {
1040       seqsfound = false;
1041       for (int i = 0; i < ids.length; i++)
1042       {
1043         if (ids[i].trim().length() == 0)
1044         {
1045           continue;
1046         }
1047         SequenceI sq = matcher.findIdMatch(ids[i]);
1048         if (sq != null)
1049         {
1050           seqsfound = true;
1051           sel.addSequence(sq, false);
1052         }
1053       }
1054     }
1055     boolean inseqpos = false;
1056     if (cols != null && cols.length > 0)
1057     {
1058       boolean seset = false;
1059       for (int i = 0; i < cols.length; i++)
1060       {
1061         String cl = cols[i].trim();
1062         if (cl.length() == 0)
1063         {
1064           continue;
1065         }
1066         int p;
1067         if ((p = cl.indexOf("-")) > -1)
1068         {
1069           int from = -1, to = -1;
1070           try
1071           {
1072             from = new Integer(cl.substring(0, p)).intValue();
1073             from--;
1074           } catch (NumberFormatException ex)
1075           {
1076             System.err.println(
1077                     "ERROR: Couldn't parse first integer in range element column selection string '"
1078                             + cl + "' - format is 'from-to'");
1079             return;
1080           }
1081           try
1082           {
1083             to = new Integer(cl.substring(p + 1)).intValue();
1084             to--;
1085           } catch (NumberFormatException ex)
1086           {
1087             System.err.println(
1088                     "ERROR: Couldn't parse second integer in range element column selection string '"
1089                             + cl + "' - format is 'from-to'");
1090             return;
1091           }
1092           if (from >= 0 && to >= 0)
1093           {
1094             // valid range
1095             if (from < to)
1096             {
1097               int t = to;
1098               to = from;
1099               to = t;
1100             }
1101             if (!seset)
1102             {
1103               start = from;
1104               end = to;
1105               seset = true;
1106             }
1107             else
1108             {
1109               // comment to prevent range extension
1110               if (start > from)
1111               {
1112                 start = from;
1113               }
1114               if (end < to)
1115               {
1116                 end = to;
1117               }
1118             }
1119             for (int r = from; r <= to; r++)
1120             {
1121               if (r >= 0 && r < alw)
1122               {
1123                 csel.addElement(r);
1124               }
1125             }
1126             if (debug)
1127             {
1128               System.err.println("Range '" + cl + "' deparsed as [" + from
1129                       + "," + to + "]");
1130             }
1131           }
1132           else
1133           {
1134             System.err.println("ERROR: Invalid Range '" + cl
1135                     + "' deparsed as [" + from + "," + to + "]");
1136           }
1137         }
1138         else
1139         {
1140           int r = -1;
1141           try
1142           {
1143             r = new Integer(cl).intValue();
1144             r--;
1145           } catch (NumberFormatException ex)
1146           {
1147             if (cl.toLowerCase().equals("sequence"))
1148             {
1149               // we are in the dataset sequence's coordinate frame.
1150               inseqpos = true;
1151             }
1152             else
1153             {
1154               System.err.println(
1155                       "ERROR: Couldn't parse integer from point selection element of column selection string '"
1156                               + cl + "'");
1157               return;
1158             }
1159           }
1160           if (r >= 0 && r <= alw)
1161           {
1162             if (!seset)
1163             {
1164               start = r;
1165               end = r;
1166               seset = true;
1167             }
1168             else
1169             {
1170               // comment to prevent range extension
1171               if (start > r)
1172               {
1173                 start = r;
1174               }
1175               if (end < r)
1176               {
1177                 end = r;
1178               }
1179             }
1180             csel.addElement(r);
1181             if (debug)
1182             {
1183               System.err.println("Point selection '" + cl
1184                       + "' deparsed as [" + r + "]");
1185             }
1186           }
1187           else
1188           {
1189             System.err.println("ERROR: Invalid Point selection '" + cl
1190                     + "' deparsed as [" + r + "]");
1191           }
1192         }
1193       }
1194     }
1195     if (seqsfound)
1196     {
1197       // we only propagate the selection when it was the null selection, or the
1198       // given sequences were found in the alignment.
1199       if (inseqpos && sel.getSize() > 0)
1200       {
1201         // assume first sequence provides reference frame ?
1202         SequenceI rs = sel.getSequenceAt(0);
1203         start = rs.findIndex(start);
1204         end = rs.findIndex(end);
1205         List<Integer> cs = new ArrayList<>(csel.getSelected());
1206         csel.clear();
1207         for (Integer selectedCol : cs)
1208         {
1209           csel.addElement(rs.findIndex(selectedCol));
1210         }
1211       }
1212       sel.setStartRes(start);
1213       sel.setEndRes(end);
1214       EventQueue.invokeLater(new Runnable()
1215       {
1216         @Override
1217         public void run()
1218         {
1219           ((AlignFrame) alf).select(sel, csel, ((AlignFrame) alf)
1220                   .getCurrentView().getAlignment().getHiddenColumns());
1221         }
1222       });
1223     }
1224   }
1225
1226   public String getAlignmentOrderFrom(AlignFrameI alf, String sep)
1227   {
1228     AlignmentI alorder = ((AlignFrame) alf).getViewport().getAlignment();
1229     String[] order = new String[alorder.getHeight()];
1230     for (int i = 0; i < order.length; i++)
1231     {
1232       order[i] = alorder.getSequenceAt(i).getName();
1233     }
1234     return arrayToSeparatorList(order, sep);
1235   }
1236
1237   public String getSelectedSequencesAsAlignmentFrom(AlignFrameI alf,
1238           String format, String suffix)
1239   {
1240     try
1241     {
1242       AlignViewport vp = ((AlignFrame) alf).getViewport();
1243       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1244       boolean seqlimits = (suffix == null
1245               || suffix.equalsIgnoreCase("true"));
1246       if (vp.getSelectionGroup() != null)
1247       {
1248         // JBPNote: getSelectionAsNewSequence behaviour has changed - this
1249         // method now returns a full copy of sequence data
1250         // TODO consider using getSequenceSelection instead here
1251         String reply = new AppletFormatAdapter().formatSequences(theFormat,
1252                 new Alignment(vp.getSelectionAsNewSequence()),
1253                 seqlimits);
1254         return reply;
1255       }
1256     } catch (IllegalArgumentException ex)
1257     {
1258       ex.printStackTrace();
1259       return "Error retrieving alignment, possibly invalid format specifier: "
1260               + format;
1261     }
1262     return "";
1263   }
1264
1265   public String orderAlignmentBy(AlignFrameI alf, String order,
1266           String undoName, String sep)
1267   {
1268     if (sep == null || sep.length() == 0)
1269     {
1270       sep = separator;
1271     }
1272     String[] ids = JalviewAppLoader.separatorListToArray(order, sep);
1273     SequenceI[] sqs = null;
1274     if (ids != null && ids.length > 0)
1275     {
1276       jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1277               ((AlignFrame) alf).getViewport().getAlignment()
1278                       .getSequencesArray());
1279       int s = 0;
1280       sqs = new SequenceI[ids.length];
1281       for (int i = 0; i < ids.length; i++)
1282       {
1283         if (ids[i].trim().length() == 0)
1284         {
1285           continue;
1286         }
1287         SequenceI sq = matcher.findIdMatch(ids[i]);
1288         if (sq != null)
1289         {
1290           sqs[s++] = sq;
1291         }
1292       }
1293       if (s > 0)
1294       {
1295         SequenceI[] sqq = new SequenceI[s];
1296         System.arraycopy(sqs, 0, sqq, 0, s);
1297         sqs = sqq;
1298       }
1299       else
1300       {
1301         sqs = null;
1302       }
1303     }
1304     if (sqs == null)
1305     {
1306       return "";
1307     }
1308     ;
1309     final AlignmentOrder aorder = new AlignmentOrder(sqs);
1310
1311     if (undoName != null && undoName.trim().length() == 0)
1312     {
1313       undoName = null;
1314     }
1315     final String _undoName = undoName;
1316     // TODO: deal with synchronization here: cannot raise any events until after
1317     // this has returned.
1318     return ((AlignFrame) alf).sortBy(aorder, _undoName) ? "true" : "";
1319   }
1320
1321   public String getAlignmentFrom(AlignFrameI alf, String format,
1322           String suffix)
1323   {
1324     try
1325     {
1326       boolean seqlimits = (suffix == null
1327               || suffix.equalsIgnoreCase("true"));
1328
1329       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1330       String reply = new AppletFormatAdapter().formatSequences(theFormat,
1331               ((AlignFrame) alf).getViewport().getAlignment(), seqlimits);
1332       return reply;
1333     } catch (IllegalArgumentException ex)
1334     {
1335       ex.printStackTrace();
1336       return "Error retrieving alignment, possibly invalid format specifier: "
1337               + format;
1338     }
1339   }
1340
1341   public void loadAnnotationFrom(AlignFrameI alf, String annotation)
1342   {
1343     if (new AnnotationFile().annotateAlignmentView(
1344             ((AlignFrame) alf).getViewport(), annotation,
1345             DataSourceType.PASTE))
1346     {
1347       ((AlignFrame) alf).alignPanel.fontChanged();
1348       ((AlignFrame) alf).alignPanel.setScrollValues(0, 0);
1349     }
1350     else
1351     {
1352       ((AlignFrame) alf).parseFeaturesFile(annotation,
1353               DataSourceType.PASTE);
1354     }
1355   }
1356
1357   public boolean loadFeaturesFrom(AlignFrameI alf, String features,
1358           boolean autoenabledisplay)
1359   {
1360     boolean ret = ((AlignFrame) alf).parseFeaturesFile(features,
1361             DataSourceType.PASTE);
1362     if (!ret)
1363     {
1364       return false;
1365     }
1366     if (autoenabledisplay)
1367     {
1368       ((AlignFrame) alf).getViewport().setShowSequenceFeatures(true);
1369       // this next was for a checkbox in JalviewLite
1370       // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
1371     }
1372     return true;
1373   }
1374
1375   public String getFeaturesFrom(AlignFrameI alf, String format)
1376   {
1377     AlignFrame f = ((AlignFrame) alf);
1378
1379     String features;
1380     FeaturesFile formatter = new FeaturesFile();
1381     if (format.equalsIgnoreCase("Jalview"))
1382     {
1383       features = formatter.printJalviewFormat(
1384               f.getViewport().getAlignment().getSequencesArray(),
1385               f.alignPanel.getFeatureRenderer(), true);
1386     }
1387     else
1388     {
1389       features = formatter.printGffFormat(
1390               f.getViewport().getAlignment().getSequencesArray(),
1391               f.alignPanel.getFeatureRenderer(), true);
1392     }
1393
1394     if (features == null)
1395     {
1396       features = "";
1397     }
1398     return features;
1399
1400   }
1401
1402   public String getAnnotationFrom(AlignFrameI alf)
1403   {
1404     AlignFrame f = (AlignFrame) alf;
1405     String annotation = new AnnotationFile()
1406             .printAnnotationsForView(f.getViewport());
1407     return annotation;
1408   }
1409
1410   public AlignFrameI newViewFrom(AlignFrameI alf, String name)
1411   {
1412     return (AlignFrameI) ((AlignFrame) alf).newView(name, true);
1413   }
1414
1415   public String[] separatorListToArray(String list)
1416   {
1417     return separatorListToArray(list, separator);
1418   }
1419
1420   public Object[] getSelectionForListener(AlignFrameI currentFrame,
1421           SequenceGroup seqsel, ColumnSelection colsel,
1422           HiddenColumns hidden, SelectionSource source, Object alignFrame)
1423   {
1424     // System.err.println("Testing selection event relay to
1425     // jsfunction:"+_listener);
1426     String setid = "";
1427     AlignFrame src = (AlignFrame) alignFrame;
1428     if (source != null)
1429     {
1430       if (source instanceof AlignViewport
1431               && ((AlignFrame) currentFrame).getViewport() == source)
1432       {
1433         // should be valid if it just generated an event!
1434         src = (AlignFrame) currentFrame;
1435
1436       }
1437     }
1438     String[] seqs = new String[] {};
1439     String[] cols = new String[] {};
1440     int strt = 0, end = (src == null) ? -1
1441             : src.alignPanel.av.getAlignment().getWidth();
1442     if (seqsel != null && seqsel.getSize() > 0)
1443     {
1444       seqs = new String[seqsel.getSize()];
1445       for (int i = 0; i < seqs.length; i++)
1446       {
1447         seqs[i] = seqsel.getSequenceAt(i).getName();
1448       }
1449       if (strt < seqsel.getStartRes())
1450       {
1451         strt = seqsel.getStartRes();
1452       }
1453       if (end == -1 || end > seqsel.getEndRes())
1454       {
1455         end = seqsel.getEndRes();
1456       }
1457     }
1458     if (colsel != null && !colsel.isEmpty())
1459     {
1460       if (end == -1)
1461       {
1462         end = colsel.getMax() + 1;
1463       }
1464       cols = new String[colsel.getSelected().size()];
1465       for (int i = 0; i < cols.length; i++)
1466       {
1467         cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
1468       }
1469     }
1470     else
1471     {
1472       if (seqsel != null && seqsel.getSize() > 0)
1473       {
1474         // send a valid range, otherwise we send the empty selection
1475         cols = new String[2];
1476         cols[0] = "" + (1 + strt) + "-" + (1 + end);
1477       }
1478     }
1479     return new Object[] { src, setid, arrayToSeparatorList(seqs),
1480         arrayToSeparatorList(cols) };
1481   }
1482
1483 }