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