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