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