JAL-2349 remove test code
[jalview.git] / src / jalview / io / AppletFormatAdapter.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  *
5  * This file is part of Jalview.
6  *
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * Jalview is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE.  See the GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import jalview.api.AlignExportSettingsI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentAnnotation;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.AlignmentView;
29 import jalview.datamodel.SeqDistanceContactMatrix;
30 import jalview.datamodel.PDBEntry.Type;
31 import jalview.datamodel.SequenceI;
32 import jalview.ext.jmol.JmolParser;
33 import jalview.structure.StructureImportSettings;
34 import jalview.util.Platform;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.util.List;
40
41 /**
42  * A low level class for alignment and feature IO with alignment formatting
43  * methods used by both applet and application for generating flat alignment
44  * files. It also holds the lists of magic format names that the applet and
45  * application will allow the user to read or write files with.
46  *
47  * @author $author$
48  * @version $Revision$
49  */
50 public class AppletFormatAdapter
51 {
52   private AlignmentViewPanel viewpanel;
53
54   /**
55    * add jalview-derived non-secondary structure annotation from PDB structure
56    */
57   boolean annotFromStructure = false;
58
59   /**
60    * add secondary structure from PDB data with built-in algorithms
61    */
62   boolean localSecondaryStruct = false;
63
64   /**
65    * process PDB data with web services
66    */
67   boolean serviceSecondaryStruct = false;
68
69   private AlignmentFileReaderI alignFile = null;
70
71   String inFile;
72
73   /**
74    * character used to write newlines
75    */
76   protected String newline = System.getProperty("line.separator");
77
78   private AlignExportSettingsI exportSettings;
79
80   private File selectedFile;
81
82   public static String INVALID_CHARACTERS = "Contains invalid characters";
83
84   /**
85    * Returns an error message with a list of supported readable file formats
86    * 
87    * @return
88    */
89   public static String getSupportedFormats()
90   {
91     return "Formats currently supported are\n"
92             + prettyPrint(FileFormats.getInstance().getReadableFormats());
93   }
94
95   public AppletFormatAdapter()
96   {
97   }
98
99   public AppletFormatAdapter(AlignmentViewPanel viewpanel)
100   {
101     this.viewpanel = viewpanel;
102   }
103
104   public AppletFormatAdapter(AlignmentViewPanel alignPanel,
105           AlignExportSettingsI settings)
106   {
107     viewpanel = alignPanel;
108     exportSettings = settings;
109   }
110
111   /**
112    * Formats a grammatically correct(ish) list consisting of the given objects
113    * 
114    * @param things
115    * @return
116    */
117   public static String prettyPrint(List<? extends Object> things)
118   {
119     StringBuffer list = new StringBuffer();
120     for (int i = 0, iSize = things.size() - 1; i < iSize; i++)
121     {
122       list.append(things.get(i).toString());
123       list.append(", ");
124     }
125     // could i18n 'and' here
126     list.append(" and " + things.get(things.size() - 1).toString() + ".");
127     return list.toString();
128   }
129
130   public void setNewlineString(String nl)
131   {
132     newline = nl;
133   }
134
135   public String getNewlineString()
136   {
137     return newline;
138   }
139
140   /**
141    * Constructs the correct filetype parser for a characterised datasource
142    *
143    * @param inFile
144    *          data/data location
145    * @param sourceType
146    *          type of datasource
147    * @param fileFormat
148    *
149    * @return
150    */
151   public AlignmentI readFile(String file, DataSourceType sourceType,
152           FileFormatI fileFormat) throws IOException
153   {
154     return readFile(null, file, sourceType, fileFormat);
155   }
156   
157   public AlignmentI readFile(File selectedFile, String file, DataSourceType sourceType,
158           FileFormatI fileFormat) throws IOException
159   {
160
161     this.selectedFile = selectedFile;
162     if (selectedFile != null)
163     {
164       this.inFile = selectedFile.getPath();
165     }
166     this.inFile = file;
167     try
168     {
169       if (fileFormat.isStructureFile())
170       {
171         String structureParser = StructureImportSettings
172                 .getDefaultPDBFileParser();
173         boolean isParseWithJMOL = structureParser.equalsIgnoreCase(
174                 StructureImportSettings.StructureParser.JMOL_PARSER
175                         .toString());
176         StructureImportSettings.addSettings(annotFromStructure,
177                 localSecondaryStruct, serviceSecondaryStruct);
178         if (isParseWithJMOL)
179         {
180           // needs a File option
181           alignFile = new JmolParser(selectedFile == null ? inFile : selectedFile, sourceType);
182         }
183         else
184         {
185           // todo is mc_view parsing obsolete yet? JAL-2120
186           StructureImportSettings.setShowSeqFeatures(true);
187           alignFile = new mc_view.PDBfile(annotFromStructure,
188                   localSecondaryStruct, serviceSecondaryStruct, inFile,
189                   sourceType);
190         }
191         ((StructureFile) alignFile).setDbRefType(
192                 FileFormat.PDB.equals(fileFormat) ? Type.PDB : Type.MMCIF);
193       }
194       else if (selectedFile != null) {
195         alignFile = fileFormat.getReader(new FileParse(selectedFile, sourceType));
196       } else 
197       {
198         // alignFile = fileFormat.getAlignmentFile(inFile, sourceType);
199         alignFile = fileFormat.getReader(new FileParse(inFile, sourceType));
200       }
201       return buildAlignmentFromFile();
202     } catch (Exception e)
203     {
204       e.printStackTrace();
205       System.err.println("Failed to read alignment using the '" + fileFormat
206               + "' reader.\n" + e);
207
208       if (e.getMessage() != null
209               && e.getMessage().startsWith(INVALID_CHARACTERS))
210       {
211         throw new IOException(e.getMessage());
212       }
213
214       // Finally test if the user has pasted just the sequence, no id
215       if (sourceType == DataSourceType.PASTE)
216       {
217         try
218         {
219           // Possible sequence is just residues with no label
220           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
221                   DataSourceType.PASTE);
222           return buildAlignmentFromFile();
223
224         } catch (Exception ex)
225         {
226           if (ex.toString().startsWith(INVALID_CHARACTERS))
227           {
228             throw new IOException(e.getMessage());
229           }
230
231           ex.printStackTrace();
232         }
233       }
234       if (FileFormat.Html.equals(fileFormat))
235       {
236         throw new IOException(e.getMessage());
237       }
238     }
239     throw new FileFormatException(getSupportedFormats());
240   }
241
242   /**
243    * Constructs the correct filetype parser for an already open datasource
244    *
245    * @param source
246    *          an existing datasource
247    * @param format
248    *          File format of data that will be provided by datasource
249    *
250    * @return
251    */
252   public AlignmentI readFromFile(FileParse source, FileFormatI format)
253           throws IOException
254   {
255     this.inFile = source.getInFile();
256     DataSourceType type = source.dataSourceType;
257     try
258     {
259       if (FileFormat.PDB.equals(format) || FileFormat.MMCif.equals(format))
260       {
261         // TODO obtain config value from preference settings
262         boolean isParseWithJMOL = false;
263         if (isParseWithJMOL)
264         {
265           StructureImportSettings.addSettings(annotFromStructure,
266                   localSecondaryStruct, serviceSecondaryStruct);
267           alignFile = new JmolParser(source);
268         }
269         else
270         {
271           StructureImportSettings.setShowSeqFeatures(true);
272           alignFile = new mc_view.PDBfile(annotFromStructure,
273                   localSecondaryStruct, serviceSecondaryStruct, source);
274         }
275         ((StructureFile) alignFile).setDbRefType(Type.PDB);
276       }
277       else
278       {
279         alignFile = format.getReader(source);
280       }
281
282       return buildAlignmentFromFile();
283
284     } catch (Exception e)
285     {
286       e.printStackTrace();
287       System.err.println("Failed to read alignment using the '" + format
288               + "' reader.\n" + e);
289
290       if (e.getMessage() != null
291               && e.getMessage().startsWith(INVALID_CHARACTERS))
292       {
293         throw new FileFormatException(e.getMessage());
294       }
295
296       // Finally test if the user has pasted just the sequence, no id
297       if (type == DataSourceType.PASTE)
298       {
299         try
300         {
301           // Possible sequence is just residues with no label
302           alignFile = new FastaFile(">UNKNOWN\n" + inFile,
303                   DataSourceType.PASTE);
304           return buildAlignmentFromFile();
305
306         } catch (Exception ex)
307         {
308           if (ex.toString().startsWith(INVALID_CHARACTERS))
309           {
310             throw new IOException(e.getMessage());
311           }
312
313           ex.printStackTrace();
314         }
315       }
316
317       // If we get to this stage, the format was not supported
318       throw new FileFormatException(getSupportedFormats());
319     }
320   }
321
322   /**
323    * boilerplate method to handle data from an AlignFile and construct a new
324    * alignment or import to an existing alignment
325    * 
326    * @return AlignmentI instance ready to pass to a UI constructor
327    */
328   private AlignmentI buildAlignmentFromFile()
329   {
330     // Standard boilerplate for creating alignment from parser
331     // alignFile.configureForView(viewpanel);
332
333     AlignmentI al = new Alignment(alignFile.getSeqsAsArray());
334
335     alignFile.addAnnotations(al);
336
337     alignFile.addGroups(al);
338
339     return al;
340   }
341
342   /**
343    * create an alignment flatfile from a Jalview alignment view
344    * 
345    * @param format
346    * @param jvsuffix
347    * @param av
348    * @param selectedOnly
349    * @return flatfile in a string
350    */
351   public String formatSequences(FileFormatI format, boolean jvsuffix,
352           AlignmentViewPanel ap, boolean selectedOnly)
353   {
354
355     AlignmentView selvew = ap.getAlignViewport()
356             .getAlignmentView(selectedOnly, false);
357     AlignmentI aselview = selvew
358             .getVisibleAlignment(ap.getAlignViewport().getGapCharacter());
359     List<AlignmentAnnotation> ala = (ap.getAlignViewport()
360             .getVisibleAlignmentAnnotation(selectedOnly));
361     if (ala != null)
362     {
363       for (AlignmentAnnotation aa : ala)
364       {
365         aselview.addAnnotation(aa);
366       }
367     }
368     viewpanel = ap;
369     return formatSequences(format, aselview, jvsuffix);
370   }
371
372   /**
373    * Construct an output class for an alignment in a particular filetype TODO:
374    * allow caller to detect errors and warnings encountered when generating
375    * output
376    *
377    * @param format
378    *          string name of alignment format
379    * @param alignment
380    *          the alignment to be written out
381    * @param jvsuffix
382    *          passed to AlnFile class controls whether /START-END is added to
383    *          sequence names
384    *
385    * @return alignment flat file contents
386    */
387   public String formatSequences(FileFormatI format, AlignmentI alignment,
388           boolean jvsuffix)
389   {
390     try
391     {
392       AlignmentFileWriterI afile = format.getWriter(alignment);
393
394       afile.setNewlineString(newline);
395       afile.setExportSettings(exportSettings);
396       afile.configureForView(viewpanel);
397
398       // check whether we were given a specific alignment to export, rather than
399       // the one in the viewpanel
400       SequenceI[] seqs = null;
401       if (viewpanel == null || viewpanel.getAlignment() == null
402               || viewpanel.getAlignment() != alignment)
403       {
404         seqs = alignment.getSequencesArray();
405       }
406       else
407       {
408         seqs = viewpanel.getAlignment().getSequencesArray();
409       }
410
411       String afileresp = afile.print(seqs, jvsuffix);
412       if (afile.hasWarningMessage())
413       {
414         System.err.println("Warning raised when writing as " + format
415                 + " : " + afile.getWarningMessage());
416       }
417       return afileresp;
418     } catch (Exception e)
419     {
420       System.err.println("Failed to write alignment as a '"
421               + format.getName() + "' file\n");
422       e.printStackTrace();
423     }
424
425     return null;
426   }
427
428
429   /**
430    * Determines the protocol (i.e DataSourceType.{FILE|PASTE|URL}) for the input
431    * data
432    * 
433    * BH 2018 allows File or String, and can return RELATIVE_URL
434    *
435    * @param dataObject File or String
436    * @return the protocol for the input data
437    */
438   public static DataSourceType checkProtocol(Object dataObject)
439   {
440     if(dataObject instanceof File)
441     {
442       return DataSourceType.FILE;
443     }
444     
445     String data = dataObject.toString();
446     DataSourceType protocol = DataSourceType.PASTE;
447     String ft = data.toLowerCase().trim();
448     if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
449             || ft.indexOf("file:") == 0)
450     {
451       protocol = DataSourceType.URL;
452     }
453     else if (Platform.isJS())
454     {
455       protocol = DataSourceType.RELATIVE_URL;
456     }
457     else if (new File(data).exists())
458     {
459       protocol = DataSourceType.FILE;
460     }
461     return protocol;
462   }
463
464   /**
465    * @param args
466    * @j2sIgnore
467    */
468   public static void main(String[] args)
469   {
470     int i = 0;
471     while (i < args.length)
472     {
473       File f = new File(args[i]);
474       if (f.exists())
475       {
476         try
477         {
478           System.out.println("Reading file: " + f);
479           AppletFormatAdapter afa = new AppletFormatAdapter();
480           Runtime r = Runtime.getRuntime();
481           System.gc();
482           long memf = -r.totalMemory() + r.freeMemory();
483           long t1 = -System.currentTimeMillis();
484           AlignmentI al = afa.readFile(args[i], DataSourceType.FILE,
485                   new IdentifyFile().identify(args[i],
486                           DataSourceType.FILE));
487           t1 += System.currentTimeMillis();
488           System.gc();
489           memf += r.totalMemory() - r.freeMemory();
490           if (al != null)
491           {
492             System.out.println("Alignment contains " + al.getHeight()
493                     + " sequences and " + al.getWidth() + " columns.");
494             try
495             {
496               System.out.println(new AppletFormatAdapter()
497                       .formatSequences(FileFormat.Fasta, al, true));
498             } catch (Exception e)
499             {
500               System.err.println(
501                       "Couln't format the alignment for output as a FASTA file.");
502               e.printStackTrace(System.err);
503             }
504           }
505           else
506           {
507             System.out.println("Couldn't read alignment");
508           }
509           System.out.println("Read took " + (t1 / 1000.0) + " seconds.");
510           System.out.println(
511                   "Difference between free memory now and before is "
512                           + (memf / (1024.0 * 1024.0) * 1.0) + " MB");
513         } catch (Exception e)
514         {
515           System.err.println("Exception when dealing with " + i
516                   + "'th argument: " + args[i] + "\n" + e);
517         }
518       }
519       else
520       {
521         System.err.println("Ignoring argument '" + args[i] + "' (" + i
522                 + "'th)- not a readable file.");
523       }
524       i++;
525     }
526   }
527
528   /**
529    * try to discover how to access the given file as a valid datasource that
530    * will be identified as the given type.
531    *
532    * @param file
533    * @param format
534    * @return protocol that yields the data parsable as the given type
535    */
536   public static DataSourceType resolveProtocol(String file,
537           FileFormatI format)
538   {
539     return resolveProtocol(file, format, false);
540   }
541
542   public static DataSourceType resolveProtocol(String file,
543           FileFormatI format, boolean debug)
544   {
545     // TODO: test thoroughly!
546     DataSourceType protocol = null;
547     if (debug)
548     {
549       System.out.println("resolving datasource started with:\n>>file\n"
550               + file + ">>endfile");
551     }
552
553     // This might throw a security exception in certain browsers
554     // Netscape Communicator for instance.
555     try
556     {
557       boolean rtn = false;
558       InputStream is = System.getSecurityManager().getClass()
559               .getResourceAsStream("/" + file);
560       if (is != null)
561       {
562         rtn = true;
563         is.close();
564       }
565       if (debug)
566       {
567         System.err.println("Resource '" + file + "' was "
568                 + (rtn ? "" : "not") + " located by classloader.");
569       }
570       if (rtn)
571       {
572         protocol = DataSourceType.CLASSLOADER;
573       }
574
575     } catch (Exception ex)
576     {
577       System.err
578               .println("Exception checking resources: " + file + " " + ex);
579     }
580
581     if (file.indexOf("://") > -1)
582     {
583       protocol = DataSourceType.URL;
584     }
585     else
586     {
587       // skipping codebase prepend check.
588       protocol = DataSourceType.FILE;
589     }
590     FileParse fp = null;
591     try
592     {
593       if (debug)
594       {
595         System.out.println(
596                 "Trying to get contents of resource as " + protocol + ":");
597       }
598       fp = new FileParse(file, protocol);
599       if (!fp.isValid())
600       {
601         fp = null;
602       }
603       else
604       {
605         if (debug)
606         {
607           System.out.println("Successful.");
608         }
609       }
610     } catch (Exception e)
611     {
612       if (debug)
613       {
614         System.err.println("Exception when accessing content: " + e);
615       }
616       fp = null;
617     }
618     if (fp == null)
619     {
620       if (debug)
621       {
622         System.out.println("Accessing as paste.");
623       }
624       protocol = DataSourceType.PASTE;
625       fp = null;
626       try
627       {
628         fp = new FileParse(file, protocol);
629         if (!fp.isValid())
630         {
631           fp = null;
632         }
633       } catch (Exception e)
634       {
635         System.err.println("Failed to access content as paste!");
636         e.printStackTrace();
637         fp = null;
638       }
639     }
640     if (fp == null)
641     {
642       return null;
643     }
644     if (format == null)
645     {
646       return protocol;
647     }
648     else
649     {
650       try
651       {
652         FileFormatI idformat = new IdentifyFile().identify(file, protocol);
653         if (idformat == null)
654         {
655           if (debug)
656           {
657             System.out.println("Format not identified. Inaccessible file.");
658           }
659           return null;
660         }
661         if (debug)
662         {
663           System.out.println("Format identified as " + idformat
664                   + "and expected as " + format);
665         }
666         if (idformat.equals(format))
667         {
668           if (debug)
669           {
670             System.out.println("Protocol identified as " + protocol);
671           }
672           return protocol;
673         }
674         else
675         {
676           if (debug)
677           {
678             System.out
679                     .println("File deemed not accessible via " + protocol);
680           }
681           fp.close();
682           return null;
683         }
684       } catch (Exception e)
685       {
686         if (debug)
687         {
688           System.err.println("File deemed not accessible via " + protocol);
689           e.printStackTrace();
690         }
691       }
692     }
693     return null;
694   }
695
696   public AlignmentFileReaderI getAlignFile()
697   {
698     return alignFile;
699   }
700 }