a7c504248b69cdef961afd649924aea414c10f99
[jalview.git] / src / jalview / bin / JalviewAppLoader.java
1 package jalview.bin;
2
3 import jalview.api.JalviewApp;
4 import jalview.api.StructureSelectionManagerProvider;
5 import jalview.datamodel.Alignment;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.AlignmentOrder;
8 import jalview.datamodel.ColumnSelection;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.datamodel.PDBEntry;
11 import jalview.datamodel.Sequence;
12 import jalview.datamodel.SequenceGroup;
13 import jalview.datamodel.SequenceI;
14 import jalview.gui.AlignFrame;
15 import jalview.gui.AlignViewport;
16 import jalview.gui.Desktop;
17 import jalview.io.AnnotationFile;
18 import jalview.io.AppletFormatAdapter;
19 import jalview.io.DataSourceType;
20 import jalview.io.FeaturesFile;
21 import jalview.io.FileFormat;
22 import jalview.io.FileFormatI;
23 import jalview.io.FileFormats;
24 import jalview.io.IdentifyFile;
25 import jalview.io.JPredFile;
26 import jalview.io.JnetAnnotationMaker;
27 import jalview.io.NewickFile;
28 import jalview.structure.SelectionSource;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.HttpUtils;
31 import jalview.util.MessageManager;
32
33 import java.awt.EventQueue;
34 import java.io.IOException;
35 import java.net.URL;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.StringTokenizer;
39 import java.util.Vector;
40
41 /**
42  * A class to load parameters for either JalviewLite or Jalview
43  * 
44  * @author hansonr
45  *
46  */
47 public class JalviewAppLoader
48 {
49
50   private JalviewApp app; // Jalview or JalviewJS or JalviewLite
51
52   private boolean debug;
53
54   String separator = "\u00AC"; // JalviewLite note: the default used to
55                                        // be '|', but many sequence IDS include
56                                        // pipes.
57
58   public String getSeparator()
59   {
60     return separator;
61   }
62
63   public void setSeparator(String separator)
64   {
65     this.separator = separator;
66   }
67
68   public JalviewAppLoader(boolean debug)
69   {
70     this.debug = debug;
71   }
72
73   public void load(JalviewApp app)
74   {
75
76     this.app = app;
77
78     String sep = app.getParameter("separator");
79     if (sep != null)
80     {
81       if (sep.length() > 0)
82       {
83         separator = sep;
84       }
85       else
86       {
87         throw new Error(MessageManager
88                 .getString("error.invalid_separator_parameter"));
89       }
90     }
91
92     loadTree();
93     loadScoreFile();
94     loadFeatures();
95     loadAnnotations();
96     loadJnetFile();
97     loadPdbFiles();
98     callInitCallback();
99   }
100
101   /**
102    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
103    * else false.
104    * 
105    * @param loaderFrame
106    * @return
107    */
108   protected boolean loadPdbFiles()
109   {
110     boolean result = false;
111     /*
112      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
113      * related to JAL-434
114      */
115
116     boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
117     app.setAlignPdbStructures(doAlign);
118     /*
119      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
120      * PDB|1GAQ|1GAQ|C">
121      * 
122      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
123      * 
124      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
125      */
126
127     // Accumulate pdbs here if they are heading for the same view (if
128     // alignPdbStructures is true)
129     Vector<Object[]> pdbs = new Vector<>();
130     // create a lazy matcher if we're asked to
131     jalview.analysis.SequenceIdMatcher matcher = (app
132             .getDefaultParameter("relaxedidmatch", false))
133                     ? new jalview.analysis.SequenceIdMatcher(
134                             app.getViewport().getAlignment()
135                                     .getSequencesArray())
136                     : null;
137
138     int pdbFileCount = 0;
139     String param;
140     do
141     {
142       if (pdbFileCount > 0)
143       {
144         param = app.getParameter("PDBFILE" + pdbFileCount);
145       }
146       else
147       {
148         param = app.getParameter("PDBFILE");
149       }
150
151       if (param != null)
152       {
153         PDBEntry pdb = new PDBEntry();
154
155         String seqstring;
156         SequenceI[] seqs = null;
157         String[] chains = null;
158
159         StringTokenizer st = new StringTokenizer(param, " ");
160
161         if (st.countTokens() < 2)
162         {
163           String sequence = app.getParameter("PDBSEQ");
164           if (sequence != null)
165           {
166             seqs = new SequenceI[] { matcher == null
167                     ? (Sequence) app.getViewport().getAlignment()
168                             .findName(sequence)
169                     : matcher.findIdMatch(sequence) };
170           }
171
172         }
173         else
174         {
175           param = st.nextToken();
176           List<SequenceI> tmp = new ArrayList<>();
177           List<String> tmp2 = new ArrayList<>();
178
179           while (st.hasMoreTokens())
180           {
181             seqstring = st.nextToken();
182             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
183             if (st2.countTokens() > 1)
184             {
185               // This is the chain
186               tmp2.add(st2.nextToken());
187               seqstring = st2.nextToken();
188             }
189             tmp.add(matcher == null
190                     ? (Sequence) app.getViewport().getAlignment()
191                             .findName(seqstring)
192                     : matcher.findIdMatch(seqstring));
193           }
194
195           seqs = tmp.toArray(new SequenceI[tmp.size()]);
196           if (tmp2.size() == tmp.size())
197           {
198             chains = tmp2.toArray(new String[tmp2.size()]);
199           }
200         }
201         pdb.setId(param);
202         ret[0] = param;
203         DataSourceType protocol = resolveFileProtocol(app, ret);
204         // TODO check JAL-357 for files in a jar (CLASSLOADER)
205         pdb.setFile(ret[0]);
206
207         if (seqs != null)
208         {
209           for (int i = 0; i < seqs.length; i++)
210           {
211             if (seqs[i] != null)
212             {
213               ((Sequence) seqs[i]).addPDBId(pdb);
214               StructureSelectionManager
215                       .getStructureSelectionManager(
216                               (StructureSelectionManagerProvider) app)
217                       .registerPDBEntry(pdb);
218             }
219             else
220             {
221               if (debug)
222               {
223                 // this may not really be a problem but we give a warning
224                 // anyway
225                 System.err.println(
226                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
227                                 + i + ")");
228               }
229             }
230           }
231
232           if (doAlign)
233           {
234             pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
235           }
236           else
237           {
238             app.newStructureView(pdb, seqs, chains, protocol);
239           }
240         }
241       }
242
243       pdbFileCount++;
244     } while (param != null || pdbFileCount < 10);
245     if (pdbs.size() > 0)
246     {
247       SequenceI[][] seqs = new SequenceI[pdbs.size()][];
248       PDBEntry[] pdb = new PDBEntry[pdbs.size()];
249       String[][] chains = new String[pdbs.size()][];
250       String[] protocols = new String[pdbs.size()];
251       for (int pdbsi = 0, pdbsiSize = pdbs
252               .size(); pdbsi < pdbsiSize; pdbsi++)
253       {
254         Object[] o = pdbs.elementAt(pdbsi);
255         pdb[pdbsi] = (PDBEntry) o[0];
256         seqs[pdbsi] = (SequenceI[]) o[1];
257         chains[pdbsi] = (String[]) o[2];
258         protocols[pdbsi] = (String) o[3];
259       }
260       app.alignedStructureView(pdb, seqs, chains, protocols);
261       result = true;
262     }
263     return result;
264   }
265
266   /**
267    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
268    * false.
269    * 
270    * @param alignFrame
271    * @return
272    */
273   protected boolean loadJnetFile()
274   {
275     boolean result = false;
276     String param = app.getParameter("jnetfile");
277     if (param == null)
278     {
279       // jnet became jpred around 2016
280       param = app.getParameter("jpredfile");
281     }
282     if (param != null)
283     {
284       try
285       {
286         ret[0] = param;
287         DataSourceType protocol = resolveFileProtocol(app, ret);
288         JPredFile predictions = new JPredFile(ret[0], protocol);
289         JnetAnnotationMaker.add_annotation(predictions,
290                 app.getViewport().getAlignment(), 0, false);
291         // false == do not add sequence profile from concise output
292         app.getViewport().getAlignment().setupJPredAlignment();
293         app.updateForAnnotations();
294         result = true;
295       } catch (Exception ex)
296       {
297         ex.printStackTrace();
298       }
299     }
300     return result;
301   }
302
303   /**
304    * Load annotations if specified by parameter. Returns true if loaded, else
305    * false.
306    * 
307    * @param alignFrame
308    * @return
309    */
310   protected boolean loadAnnotations()
311   {
312     boolean result = false;
313     String param = app.getParameter("annotations");
314     if (param != null)
315     {
316       ret[0] = param;
317       DataSourceType protocol = resolveFileProtocol(app, ret);
318       param = ret[0];
319       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
320               param, protocol))
321       {
322         app.updateForAnnotations();
323         result = true;
324       }
325       else
326       {
327         System.err
328                 .println("Annotations were not added from annotation file '"
329                         + param + "'");
330       }
331     }
332     return result;
333   }
334
335   /**
336    * Load features file and view settings as specified by parameters. Returns
337    * true if features were loaded, else false.
338    * 
339    * @param alignFrame
340    * @return
341    */
342   protected boolean loadFeatures()
343   {
344     boolean result = false;
345     // ///////////////////////////
346     // modify display of features
347     // we do this before any features have been loaded, ensuring any hidden
348     // groups are hidden when features first displayed
349     //
350     // hide specific groups
351     //
352     String param = app.getParameter("hidefeaturegroups");
353     if (param != null)
354     {
355       app.setFeatureGroupState(separatorListToArray(param, separator),
356               false);
357       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
358     }
359     // show specific groups
360     param = app.getParameter("showfeaturegroups");
361     if (param != null)
362     {
363       app.setFeatureGroupState(separatorListToArray(param, separator),
364               true);
365       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
366     }
367     // and now load features
368     param = app.getParameter("features");
369     if (param != null)
370     {
371       ret[0] = param;
372       DataSourceType protocol = resolveFileProtocol(app, ret);
373
374       result = app.parseFeaturesFile(ret[0], protocol);
375     }
376
377     param = app.getParameter("showFeatureSettings");
378     if (param != null && param.equalsIgnoreCase("true"))
379     {
380       app.newFeatureSettings();
381     }
382     return result;
383   }
384
385   /**
386    * Load a score file if specified by parameter. Returns true if file was
387    * loaded, else false.
388    * 
389    * @param loaderFrame
390    */
391   protected boolean loadScoreFile()
392   {
393     boolean result = false;
394     String sScoreFile = app.getParameter("scoreFile");
395     if (sScoreFile != null && !"".equals(sScoreFile))
396     {
397       try
398       {
399         if (debug)
400         {
401           System.err.println(
402                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
403         }
404         result = app.loadScoreFile(sScoreFile);
405         if (!result)
406         {
407           System.err.println(
408                   "Failed to parse T-COFFEE parameter as a valid score file ('"
409                           + sScoreFile + "')");
410         }
411       } catch (Exception e)
412       {
413         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
414                 sScoreFile, e.getMessage());
415       }
416     }
417     return result;
418   }
419
420   String[] ret = new String[1];
421
422   /**
423    * Load a tree for the alignment if specified by parameter. Returns true if a
424    * tree was loaded, else false.
425    * 
426    * @param loaderFrame
427    * @return
428    */
429   protected boolean loadTree()
430   {
431     boolean result = false;
432     String treeFile = app.getParameter("tree");
433     if (treeFile == null)
434     {
435       treeFile = app.getParameter("treeFile");
436     }
437
438     if (treeFile != null)
439     {
440       try
441       {
442         ret[0] = treeFile;
443         NewickFile fin = new NewickFile(treeFile,
444                 resolveFileProtocol(app, ret));
445         fin.parse();
446
447         if (fin.getTree() != null)
448         {
449           app.loadTree(fin, ret[0]);
450           result = true;
451           if (debug)
452           {
453             System.out.println("Successfully imported tree.");
454           }
455         }
456         else
457         {
458           if (debug)
459           {
460             System.out.println(
461                     "Tree parameter did not resolve to a valid tree.");
462           }
463         }
464       } catch (Exception ex)
465       {
466         ex.printStackTrace();
467       }
468     }
469     return result;
470   }
471
472   /**
473    * form a complete URL given a path to a resource and a reference location on
474    * the same server
475    * 
476    * @param targetPath
477    *          - an absolute path on the same server as localref or a document
478    *          located relative to localref
479    * @param localref
480    *          - a URL on the same server as url
481    * @return a complete URL for the resource located by url
482    */
483   public static String resolveUrlForLocalOrAbsolute(String targetPath,
484           URL localref)
485   {
486     String resolvedPath = "";
487     if (targetPath.startsWith("/"))
488     {
489       String codebase = localref.toString();
490       String localfile = localref.getFile();
491       resolvedPath = codebase.substring(0,
492               codebase.length() - localfile.length()) + targetPath;
493       return resolvedPath;
494     }
495
496     /*
497      * get URL path and strip off any trailing file e.g.
498      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
499      * www.jalview.org/examples/
500      */
501     String urlPath = localref.toString();
502     String directoryPath = urlPath;
503     int lastSeparator = directoryPath.lastIndexOf("/");
504     if (lastSeparator > 0)
505     {
506       directoryPath = directoryPath.substring(0, lastSeparator + 1);
507     }
508
509     if (targetPath.startsWith("/"))
510     {
511       /*
512        * construct absolute URL to a file on the server - this is not allowed?
513        */
514       // String localfile = localref.getFile();
515       // resolvedPath = urlPath.substring(0,
516       // urlPath.length() - localfile.length())
517       // + targetPath;
518       resolvedPath = directoryPath + targetPath.substring(1);
519     }
520     else
521     {
522       resolvedPath = directoryPath + targetPath;
523     }
524     // if (debug)
525     // {
526     // System.err.println(
527     // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
528     // }
529     return resolvedPath;
530   }
531
532   /**
533    * parse the string into a list
534    * 
535    * @param list
536    * @param separator
537    * @return elements separated by separator
538    */
539   public static String[] separatorListToArray(String list, String separator)
540   {
541     // TODO use StringUtils version (slightly different...)
542     int seplen = separator.length();
543     if (list == null || list.equals("") || list.equals(separator))
544     {
545       return null;
546     }
547     Vector<String> jv = new Vector<>();
548     int cp = 0, pos;
549     while ((pos = list.indexOf(separator, cp)) > cp)
550     {
551       jv.addElement(list.substring(cp, pos));
552       cp = pos + seplen;
553     }
554     if (cp < list.length())
555     {
556       String c = list.substring(cp);
557       if (!c.equals(separator))
558       {
559         jv.addElement(c);
560       }
561     }
562     if (jv.size() > 0)
563     {
564       String[] v = new String[jv.size()];
565       for (int i = 0; i < v.length; i++)
566       {
567         v[i] = jv.elementAt(i);
568       }
569       jv.removeAllElements();
570       // if (debug)
571       // {
572       // System.err.println("Array from '" + separator
573       // + "' separated List:\n" + v.length);
574       // for (int i = 0; i < v.length; i++)
575       // {
576       // System.err.println("item " + i + " '" + v[i] + "'");
577       // }
578       // }
579       return v;
580     }
581     // if (debug)
582     // {
583     // System.err.println(
584     // "Empty Array from '" + separator + "' separated List");
585     // }
586     return null;
587   }
588
589   public static DataSourceType resolveFileProtocol(JalviewApp app,
590           String[] retPath)
591   {
592     String path = retPath[0];
593     /*
594      * is it paste data?
595      */
596     if (path.startsWith("PASTE"))
597     {
598       retPath[0] = path.substring(5);
599       return DataSourceType.PASTE;
600     }
601
602     /*
603      * is it a URL?
604      */
605     if (path.indexOf("://") >= 0)
606     {
607       return DataSourceType.URL;
608     }
609
610     /*
611      * try relative to document root
612      */
613     URL documentBase = app.getDocumentBase();
614     String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
615     if (HttpUtils.isValidUrl(withDocBase))
616     {
617       // if (debug)
618       // {
619       // System.err.println("Prepended document base '" + documentBase
620       // + "' to make: '" + withDocBase + "'");
621       // }
622       retPath[0] = withDocBase;
623       return DataSourceType.URL;
624     }
625
626     /*
627      * try relative to codebase (if different to document base)
628      */
629     URL codeBase = app.getCodeBase();
630     String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
631     if (!withCodeBase.equals(withDocBase)
632             && HttpUtils.isValidUrl(withCodeBase))
633     {
634       // if (debug)
635       // {
636       // System.err.println("Prepended codebase '" + codeBase
637       // + "' to make: '" + withCodeBase + "'");
638       // }
639       retPath[0] = withCodeBase;
640       return DataSourceType.URL;
641     }
642
643     /*
644      * try locating by classloader; try this last so files in the directory
645      * are resolved using document base
646      */
647     if (inArchive(app.getClass(), path))
648     {
649       return DataSourceType.CLASSLOADER;
650     }
651     return null;
652   }
653
654   /**
655    * Discovers whether the given file is in the Applet Archive
656    * 
657    * @param f
658    *          String
659    * @return boolean
660    */
661   private static boolean inArchive(Class<?> c, String f)
662   {
663     // This might throw a security exception in certain browsers
664     // Netscape Communicator for instance.
665     try
666     {
667       boolean rtn = (c.getResourceAsStream("/" + f) != null);
668       // if (debug)
669       // {
670       // System.err.println("Resource '" + f + "' was "
671       // + (rtn ? "" : "not ") + "located by classloader.");
672       // }
673       return rtn;
674     } catch (Exception ex)
675     {
676       System.out.println("Exception checking resources: " + f + " " + ex);
677       return false;
678     }
679   }
680
681   public void callInitCallback()
682   {
683     String initjscallback = app.getParameter("oninit");
684     if (initjscallback == null)
685     {
686       return;
687     }
688     initjscallback = initjscallback.trim();
689     if (initjscallback.length() > 0)
690     {
691       // TODO
692     }
693   }
694
695   /**
696    * read sequence1...sequenceN as a raw alignment
697    * 
698    * @param jalviewApp
699    * @return
700    */
701   public String getPastedSequence(JalviewApp jalviewApp)
702   {
703     StringBuffer data = new StringBuffer("PASTE");
704     int i = 1;
705     String file = null;
706     while ((file = app.getParameter("sequence" + i)) != null)
707     {
708       data.append(file.toString() + "\n");
709       i++;
710     }
711     if (data.length() > 5)
712     {
713       file = data.toString();
714     }
715     return file;
716   }
717
718   /**
719    * concatenate the list with separator
720    * 
721    * @param list
722    * @param separator
723    * @return concatenated string
724    */
725   public static String arrayToSeparatorList(String[] list, String separator)
726   {
727     // TODO use StringUtils version
728     StringBuffer v = new StringBuffer();
729     if (list != null && list.length > 0)
730     {
731       for (int i = 0, iSize = list.length; i < iSize; i++)
732       {
733         if (list[i] != null)
734         {
735           if (i > 0)
736           {
737             v.append(separator);
738           }
739           v.append(list[i]);
740         }
741       }
742       // if (debug)
743       // {
744       // System.err
745       // .println("Returning '" + separator + "' separated List:\n");
746       // System.err.println(v);
747       // }
748       return v.toString();
749     }
750     // if (debug)
751     // {
752     // System.err.println(
753     // "Returning empty '" + separator + "' separated List\n");
754     // }
755     return "" + separator;
756   }
757
758   public String arrayToSeparatorList(String[] array)
759   {
760     return arrayToSeparatorList(array, separator);
761   }
762
763   public String getSelectedSequencesFrom(AlignFrame alf, String sep)
764   {
765     StringBuffer result = new StringBuffer("");
766     if (sep == null || sep.length() == 0)
767     {
768       sep = separator; // "+0x00AC;
769     }
770     AlignViewport v = alf.getViewport();
771     if (v.getSelectionGroup() != null)
772     {
773       SequenceI[] seqs = v.getSelectionGroup()
774               .getSequencesInOrder(v.getAlignment());
775
776       for (int i = 0; i < seqs.length; i++)
777       {
778         result.append(seqs[i].getName());
779         result.append(sep);
780       }
781     }
782
783     return result.toString();
784   }
785
786   public void setFeatureGroupStateOn(final AlignFrame alf,
787           final String groups, boolean state)
788   {
789     java.awt.EventQueue.invokeLater(new Runnable()
790     {
791       @Override
792       public void run()
793       {
794         alf.setFeatureGroupState(
795                 separatorListToArray(groups, separator), state);
796       }
797     });
798   }
799
800   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
801   {
802     return arrayToSeparatorList(
803             alf.getFeatureGroupsOfState(visible));
804   }
805
806   public void scrollViewToIn(final AlignFrame alf, final String topRow,
807           final String leftHandColumn)
808   {
809     java.awt.EventQueue.invokeLater(new Runnable()
810     {
811       @Override
812       public void run()
813       {
814         try
815         {
816           alf.scrollTo(Integer.valueOf(topRow).intValue(),
817                   Integer.valueOf(leftHandColumn).intValue());
818
819         } catch (Exception ex)
820         {
821           System.err.println("Couldn't parse integer arguments (topRow='"
822                   + topRow + "' and leftHandColumn='" + leftHandColumn
823                   + "')");
824           ex.printStackTrace();
825         }
826       }
827     });
828   }
829
830   public void scrollViewToRowIn(final AlignFrame alf, final String topRow)
831   {
832
833     java.awt.EventQueue.invokeLater(new Runnable()
834     {
835       @Override
836       public void run()
837       {
838         try
839         {
840           alf.scrollToRow(Integer.valueOf(topRow).intValue());
841
842         } catch (Exception ex)
843         {
844           System.err.println("Couldn't parse integer arguments (topRow='"
845                   + topRow + "')");
846           ex.printStackTrace();
847         }
848
849       }
850     });
851   }
852
853   public void scrollViewToColumnIn(final AlignFrame alf,
854           final String leftHandColumn)
855   {
856     java.awt.EventQueue.invokeLater(new Runnable()
857     {
858
859       @Override
860       public void run()
861       {
862         try
863         {
864           alf
865                   .scrollToColumn(
866                           Integer.valueOf(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(AlignFrame alf, String sequenceId,
881           String pdbEntryString, String pdbFile)
882   {
883     AlignFrame alFrame = 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 AlignFrame 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(AlignFrame alf)
956   {
957     return arrayToSeparatorList(
958             alf.getFeatureGroups());
959   }
960
961   public void highlightIn(final AlignFrame 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             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 = Integer.valueOf(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 AlignFrame 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 = alf.getViewport().getAlignment();
1033     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1034             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 = Integer.valueOf(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 = Integer.valueOf(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 = Integer.valueOf(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           alf.select(sel, csel, alf
1220                   .getCurrentView().getAlignment().getHiddenColumns());
1221         }
1222       });
1223     }
1224   }
1225
1226   public String getAlignmentOrderFrom(AlignFrame alf, String sep)
1227   {
1228     AlignmentI alorder = 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(AlignFrame alf,
1238           String format, String suffix)
1239   {
1240     try
1241     {
1242       AlignViewport vp = 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(AlignFrame 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               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 alf.sortBy(aorder, _undoName) ? "true" : "";
1319   }
1320
1321   public String getAlignmentFrom(AlignFrame 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               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(AlignFrame alf, String annotation)
1342   {
1343     if (new AnnotationFile().annotateAlignmentView(
1344             alf.getViewport(), annotation,
1345             DataSourceType.PASTE))
1346     {
1347       alf.alignPanel.fontChanged();
1348       alf.alignPanel.setScrollValues(0, 0);
1349     }
1350     else
1351     {
1352       alf.parseFeaturesFile(annotation,
1353               DataSourceType.PASTE);
1354     }
1355   }
1356
1357   public boolean loadFeaturesFrom(AlignFrame alf, String features,
1358           boolean autoenabledisplay)
1359   {
1360     boolean ret = alf.parseFeaturesFile(features,
1361             DataSourceType.PASTE);
1362     if (!ret)
1363     {
1364       return false;
1365     }
1366     if (autoenabledisplay)
1367     {
1368       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(AlignFrame alf, String format)
1376   {
1377     AlignFrame f = (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(AlignFrame alf)
1403   {
1404     AlignFrame f = alf;
1405     String annotation = new AnnotationFile()
1406             .printAnnotationsForView(f.getViewport());
1407     return annotation;
1408   }
1409
1410   // public AlignFrame newViewFrom(AlignFrame alf, String name)
1411   // {
1412   // return (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(AlignFrame 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               && currentFrame.getViewport() == source)
1432       {
1433         // should be valid if it just generated an event!
1434         src = 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 }