1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2021-11-29 20:21:31 -0600 (Mon, 29 Nov 2021) $
4  * $Revision: 22264 $
5  *
6  * Copyright (C) 2003-2005  Miguel, Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package org.jmol.viewer;
25 
26 import java.io.BufferedInputStream;
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 import java.net.URLEncoder;
34 import java.util.Hashtable;
35 import java.util.Map;
36 
37 import org.jmol.adapter.readers.spartan.SpartanUtil;
38 import org.jmol.api.GenericFileInterface;
39 import org.jmol.api.Interface;
40 import org.jmol.api.JmolDomReaderInterface;
41 import org.jmol.api.JmolFilesReaderInterface;
42 import org.jmol.io.FileReader;
43 import org.jmol.io.JmolUtil;
44 import org.jmol.script.SV;
45 import org.jmol.script.T;
46 import org.jmol.util.Escape;
47 import org.jmol.util.Logger;
48 import org.jmol.viewer.Viewer.ACCESS;
49 
50 import javajs.J2SIgnoreImport;
51 import javajs.api.BytePoster;
52 import javajs.util.AU;
53 import javajs.util.BArray;
54 import javajs.util.Base64;
55 import javajs.util.CompoundDocument;
56 import javajs.util.DataReader;
57 import javajs.util.LimitedLineReader;
58 import javajs.util.Lst;
59 import javajs.util.OC;
60 import javajs.util.PT;
61 import javajs.util.Rdr;
62 import javajs.util.SB;
63 
64 
65 @J2SIgnoreImport({Rdr.StreamReader.class})
66 public class FileManager implements BytePoster {
67 
68   public static String SIMULATION_PROTOCOL = "http://SIMULATION/";
69 
70   public Viewer vwr;
71 
72 
FileManager(Viewer vwr)73   FileManager(Viewer vwr) {
74     this.vwr = vwr;
75     clear();
76   }
77 
78   private SpartanUtil spartanDoc;
79 
80   /**
81    * An isolated class to retrieve Spartan file data from compound documents, zip files, and directories
82    * @return a SpartanUtil
83    */
spartanUtil()84   public SpartanUtil spartanUtil() {
85     return (spartanDoc == null ? spartanDoc = ((SpartanUtil) Interface.getInterface("org.jmol.adapter.readers.spartan.SpartanUtil", vwr, "fm getSpartanUtil()")).set(this) : spartanDoc);
86   }
87 
88   JmolUtil jzu;
89 
getJzu()90   public JmolUtil getJzu() {
91     return (jzu == null ? jzu = (JmolUtil) Interface.getOption("io.JmolUtil", vwr, "file") : jzu);
92   }
93 
clear()94   void clear() {
95     // from zap
96     setFileInfo(new String[] { vwr.getZapName() });
97     spardirCache = null;
98   }
99 
setLoadState(Map<String, Object> htParams)100   private void setLoadState(Map<String, Object> htParams) {
101     if (vwr.getPreserveState()) {
102       htParams.put("loadState", vwr.g.getLoadState(htParams));
103     }
104   }
105 
106   private String pathForAllFiles = ""; // leave private because of setPathForAllFiles
107 
getPathForAllFiles()108   public String getPathForAllFiles() {
109     return pathForAllFiles;
110   }
111 
setPathForAllFiles(String value)112   String setPathForAllFiles(String value) {
113     if (value.length() > 0 && !value.endsWith("/") && !value.endsWith("|"))
114         value += "/";
115     return pathForAllFiles = value;
116   }
117 
118   private String nameAsGiven = JC.ZAP_TITLE, fullPathName, lastFullPathName, lastNameAsGiven = JC.ZAP_TITLE, fileName;
119 
120   /**
121    * Set fullPathName, fileName, and nameAsGiven
122    *
123    * @param fileInfo if null, replace fullPathName and nameAsGiven with last version of such
124    *
125    *
126    */
setFileInfo(String[] fileInfo)127   public void setFileInfo(String[] fileInfo) {
128     if (fileInfo == null) {
129       fullPathName = lastFullPathName;
130       nameAsGiven = lastNameAsGiven;
131       return;
132     }
133     // used by ScriptEvaluator dataFrame and load methods to temporarily save the state here
134     fullPathName = fileInfo[0];
135     fileName = fileInfo[Math.min(1,  fileInfo.length - 1)];
136     nameAsGiven = fileInfo[Math.min(2, fileInfo.length - 1)];
137     if (!nameAsGiven.equals(JC.ZAP_TITLE)) {
138       lastNameAsGiven = nameAsGiven;
139       lastFullPathName = fullPathName;
140     }
141   }
142 
getFileInfo()143   public String[] getFileInfo() {
144     // used by ScriptEvaluator dataFrame method
145     return new String[] { fullPathName, fileName, nameAsGiven };
146   }
147 
getFullPathName(boolean orPrevious)148   public String getFullPathName(boolean orPrevious) {
149     String f =(fullPathName != null ? fullPathName : nameAsGiven);
150     return (!orPrevious || !f.equals(JC.ZAP_TITLE) ? f : lastFullPathName != null ? lastFullPathName : lastNameAsGiven);
151   }
152 
getFileName()153   public String getFileName() {
154     return fileName != null ? fileName : nameAsGiven;
155   }
156 
157   // for applet proxy
158   private URL appletDocumentBaseURL = null;
159   private String appletProxy;
160 
getAppletDocumentBase()161   String getAppletDocumentBase() {
162     return (appletDocumentBaseURL == null ? "" : appletDocumentBaseURL.toString());
163   }
164 
setAppletContext(String documentBase)165   void setAppletContext(String documentBase) {
166     try {
167       System.out.println("setting document base to \"" + documentBase + "\"");
168       appletDocumentBaseURL = (documentBase.length() == 0 ? null : new URL((URL) null, documentBase, null));
169     } catch (MalformedURLException e) {
170       System.out.println("error setting document base to " + documentBase);
171     }
172   }
173 
setAppletProxy(String appletProxy)174   void setAppletProxy(String appletProxy) {
175     this.appletProxy = (appletProxy == null || appletProxy.length() == 0 ? null
176         : appletProxy);
177   }
178 
179 
180   /////////////// createAtomSetCollectionFromXXX methods /////////////////
181 
182   // where XXX = File, Files, String, Strings, ArrayData, DOM, Reader
183 
184   /*
185    * note -- createAtomSetCollectionFromXXX methods
186    * were "openXXX" before refactoring 11/29/2008 -- BH
187    *
188    * The problem was that while they did open the file, they
189    * (mostly) also closed them, and this was confusing.
190    *
191    * The term "clientFile" was replaced by "atomSetCollection"
192    * here because that's what it is --- an AtomSetCollection,
193    * not a file. The file is closed at this point. What is
194    * returned is the atomSetCollection object.
195    *
196    * One could say this is just semantics, but there were
197    * subtle bugs here, where readers were not always being
198    * closed explicitly. In the process of identifying Out of
199    * Memory Errors, I felt it was necessary to clarify all this.
200    *
201    * Apologies to those who feel the original clientFile notation
202    * was more generalizable or understandable.
203    *
204    */
createAtomSetCollectionFromFile(String name, Map<String, Object> htParams, boolean isAppend)205   Object createAtomSetCollectionFromFile(String name,
206                                          Map<String, Object> htParams,
207                                          boolean isAppend) {
208     if (htParams.get("atomDataOnly") == null)
209       setLoadState(htParams);
210     String name0 = name;
211     name = vwr.resolveDatabaseFormat(name);
212     if (!name0.equals(name) && name0.indexOf("/") < 0
213         && Viewer.hasDatabasePrefix(name0)) {
214       htParams.put("dbName", name0);
215     }
216     if (name.endsWith("%2D%")) {
217       String filter = (String) htParams.get("filter");
218       htParams.put("filter", (filter == null ? "" : filter) + "2D");
219       name = name.substring(0, name.length() - 4);
220     }
221 
222     int pt = name.indexOf("::");
223     String nameAsGiven = (pt >= 0 ? name.substring(pt + 2) : name);
224     String fileType = (pt >= 0 ? name.substring(0, pt) : null);
225     Logger.info("\nFileManager.getAtomSetCollectionFromFile(" + nameAsGiven
226         + ")" + (name.equals(nameAsGiven) ? "" : " //" + name));
227     String[] names = getClassifiedName(nameAsGiven, true);
228     if (names.length == 1)
229       return names[0];
230     String fullPathName = names[0];
231     String fileName = names[1];
232     htParams.put("fullPathName", (fileType == null ? "" : fileType + "::")
233         + fixDOSName(fullPathName));
234     if (vwr.getBoolean(T.messagestylechime) && vwr.getBoolean(T.debugscript))
235       vwr.getChimeMessenger().update(fullPathName);
236     FileReader fileReader = new FileReader(vwr, fileName, fullPathName, nameAsGiven,
237         fileType, null, htParams, isAppend);
238     fileReader.run();
239     return fileReader.getAtomSetCollection();
240   }
241 
createAtomSetCollectionFromFiles(String[] fileNames, Map<String, Object> htParams, boolean isAppend)242   Object createAtomSetCollectionFromFiles(String[] fileNames,
243                                           Map<String, Object> htParams,
244                                           boolean isAppend) {
245     setLoadState(htParams);
246     String[] fullPathNames = new String[fileNames.length];
247     String[] namesAsGiven = new String[fileNames.length];
248     String[] fileTypes = new String[fileNames.length];
249     for (int i = 0; i < fileNames.length; i++) {
250       int pt = fileNames[i].indexOf("::");
251       String nameAsGiven = (pt >= 0 ? fileNames[i].substring(pt + 2)
252           : fileNames[i]);
253       String fileType = (pt >= 0 ? fileNames[i].substring(0, pt) : null);
254       String[] names = getClassifiedName(nameAsGiven, true);
255       if (names.length == 1)
256         return names[0];
257       fullPathNames[i] = names[0];
258       fileNames[i] = fixDOSName(names[0]);
259       fileTypes[i] = fileType;
260       namesAsGiven[i] = nameAsGiven;
261     }
262     htParams.put("fullPathNames", fullPathNames);
263     htParams.put("fileTypes", fileTypes);
264     JmolFilesReaderInterface filesReader = newFilesReader(fullPathNames, namesAsGiven,
265         fileTypes, null, htParams, isAppend);
266     filesReader.run();
267     return filesReader.getAtomSetCollection();
268   }
269 
createAtomSetCollectionFromString(String strModel, Map<String, Object> htParams, boolean isAppend)270   Object createAtomSetCollectionFromString(String strModel,
271                                            Map<String, Object> htParams,
272                                            boolean isAppend) {
273     setLoadState(htParams);
274     boolean isAddH = (strModel.indexOf(JC.ADD_HYDROGEN_TITLE) >= 0);
275     String[] fnames = (isAddH ? getFileInfo() : null);
276     FileReader fileReader = new FileReader(vwr, "string", null, null, null,
277         Rdr.getBR(strModel), htParams, isAppend);
278     fileReader.run();
279     if (fnames != null)
280       setFileInfo(fnames);
281     if (!isAppend && !(fileReader.getAtomSetCollection() instanceof String)) {
282 // zap is unnecessary  - it was done already in FileReader, and it
283 // inappropriately clears the PDB chain name map
284 //      vwr.zap(false, true, false);
285       setFileInfo(new String[] { strModel == JC.MODELKIT_ZAP_STRING ? JC.MODELKIT_ZAP_TITLE
286           : "string"});
287     }
288     return fileReader.getAtomSetCollection();
289   }
290 
createAtomSeCollectionFromStrings(String[] arrayModels, SB loadScript, Map<String, Object> htParams, boolean isAppend)291   Object createAtomSeCollectionFromStrings(String[] arrayModels,
292                                            SB loadScript,
293                                            Map<String, Object> htParams,
294                                            boolean isAppend) {
295     if (!htParams.containsKey("isData")) {
296       String oldSep = "\"" + vwr.getDataSeparator() + "\"";
297       String tag = "\"" + (isAppend ? "append" : "model") + " inline\"";
298       SB sb = new SB();
299       sb.append("set dataSeparator \"~~~next file~~~\";\ndata ").append(tag);
300       for (int i = 0; i < arrayModels.length; i++) {
301         if (i > 0)
302           sb.append("~~~next file~~~");
303         sb.append(arrayModels[i]);
304       }
305       sb.append("end ").append(tag).append(";set dataSeparator ")
306           .append(oldSep);
307       loadScript.appendSB(sb);
308     }
309     setLoadState(htParams);
310     Logger.info("FileManager.getAtomSetCollectionFromStrings(string[])");
311     String[] fullPathNames = new String[arrayModels.length];
312     DataReader[] readers = new DataReader[arrayModels.length];
313     for (int i = 0; i < arrayModels.length; i++) {
314       fullPathNames[i] = "string[" + i + "]";
315       readers[i] = newDataReader(vwr, arrayModels[i]);
316     }
317     JmolFilesReaderInterface filesReader = newFilesReader(fullPathNames, fullPathNames,
318         null, readers, htParams, isAppend);
319     filesReader.run();
320     return filesReader.getAtomSetCollection();
321   }
322 
createAtomSeCollectionFromArrayData(Lst<Object> arrayData, Map<String, Object> htParams, boolean isAppend)323   Object createAtomSeCollectionFromArrayData(Lst<Object> arrayData,
324                                              Map<String, Object> htParams,
325                                              boolean isAppend) {
326     // NO STATE SCRIPT -- HERE WE ARE TRYING TO CONSERVE SPACE
327     Logger.info("FileManager.getAtomSetCollectionFromArrayData(Vector)");
328     int nModels = arrayData.size();
329     String[] fullPathNames = new String[nModels];
330     DataReader[] readers = new DataReader[nModels];
331     for (int i = 0; i < nModels; i++) {
332       fullPathNames[i] = "String[" + i + "]";
333       readers[i] = newDataReader(vwr, arrayData.get(i));
334     }
335     JmolFilesReaderInterface filesReader = newFilesReader(fullPathNames, fullPathNames,
336         null, readers, htParams, isAppend);
337     filesReader.run();
338     return filesReader.getAtomSetCollection();
339   }
340 
newDataReader(Viewer vwr, Object data)341   static DataReader newDataReader(Viewer vwr, Object data) {
342     String reader = (data instanceof String ? "String"
343         : AU.isAS(data) ? "Array"
344         : data instanceof Lst<?> ? "List" : null);
345     if (reader == null)
346       return null;
347     DataReader dr = (DataReader) Interface.getInterface("javajs.util." + reader + "DataReader", vwr, "file");
348     return dr.setData(data);
349   }
350 
newFilesReader(String[] fullPathNames, String[] namesAsGiven, String[] fileTypes, DataReader[] readers, Map<String, Object> htParams, boolean isAppend)351   private JmolFilesReaderInterface newFilesReader(String[] fullPathNames,
352                                                   String[] namesAsGiven,
353                                                   String[] fileTypes,
354                                                   DataReader[] readers,
355                                                   Map<String, Object> htParams,
356                                                   boolean isAppend) {
357     JmolFilesReaderInterface fr = (JmolFilesReaderInterface) Interface
358         .getOption("io.FilesReader", vwr, "file");
359     fr.set(this, vwr, fullPathNames, namesAsGiven, fileTypes, readers, htParams,
360         isAppend);
361     return fr;
362   }
363 
createAtomSetCollectionFromDOM(Object DOMNode, Map<String, Object> htParams)364   Object createAtomSetCollectionFromDOM(Object DOMNode,
365                                         Map<String, Object> htParams) {
366     JmolDomReaderInterface aDOMReader = (JmolDomReaderInterface) Interface.getOption("io.DOMReader", vwr, "file");
367     aDOMReader.set(this, vwr, DOMNode, htParams);
368     aDOMReader.run();
369     return aDOMReader.getAtomSetCollection();
370   }
371 
372   /**
373    * not used in Jmol project -- will close reader
374    *
375    * @param fullPathName
376    * @param name
377    * @param reader could be a Reader, or a BufferedInputStream or byte[]
378    * @param htParams
379    * @return fileData
380    */
createAtomSetCollectionFromReader(String fullPathName, String name, Object reader, Map<String, Object> htParams)381   Object createAtomSetCollectionFromReader(String fullPathName, String name,
382                                            Object reader,
383                                            Map<String, Object> htParams) {
384     FileReader fileReader = new FileReader(vwr, name, fullPathName, null, null,
385         reader, htParams, false);
386     fileReader.run();
387     return fileReader.getAtomSetCollection();
388   }
389 
390   /////////////// generally useful file I/O methods /////////////////
391 
392   // mostly internal to FileManager and its enclosed classes
393 
getBufferedInputStream(String fullPathName)394   BufferedInputStream getBufferedInputStream(String fullPathName) {
395     Object ret = getBufferedReaderOrErrorMessageFromName(fullPathName,
396         new String[2], true, true);
397     return (ret instanceof BufferedInputStream ? (BufferedInputStream) ret
398         : null);
399   }
400 
getBufferedInputStreamOrErrorMessageFromName(String name, String fullName, boolean showMsg, boolean checkOnly, byte[] outputBytes, boolean allowReader, boolean allowCached)401   public Object getBufferedInputStreamOrErrorMessageFromName(String name,
402                                                              String fullName,
403                                                              boolean showMsg,
404                                                              boolean checkOnly,
405                                                              byte[] outputBytes,
406                                                              boolean allowReader,
407                                                              boolean allowCached) {
408     BufferedInputStream bis = null;
409     Object ret = null;
410     String errorMessage = null;
411     byte[] cacheBytes = (allowCached && outputBytes == null ? cacheBytes = getPngjOrDroppedBytes(
412         fullName, name) : null);
413     try {
414       if (allowCached && name.indexOf(".png") >= 0 && pngjCache == null
415           && !vwr.getBoolean(T.testflag1))
416         pngjCache = new Hashtable<String, Object>();
417       if (cacheBytes == null) {
418         boolean isPngjBinaryPost = (name.indexOf("?POST?_PNGJBIN_") >= 0);
419         boolean isPngjPost = (isPngjBinaryPost || name.indexOf("?POST?_PNGJ_") >= 0);
420         if (name.indexOf("?POST?_PNG_") > 0 || isPngjPost) {
421           String[] errMsg = new String[1];
422           byte[] bytes = vwr.getImageAsBytes(isPngjPost ? "PNGJ" : "PNG", 0, 0,
423               -1, errMsg);
424           if (errMsg[0] != null)
425             return errMsg[0];
426           if (isPngjBinaryPost) {
427             outputBytes = bytes;
428             name = PT.rep(name, "?_", "=_");
429           } else {
430             name = new SB().append(name).append("=")
431                 .appendSB(Base64.getBase64(bytes)).toString();
432           }
433         }
434         int iurl = OC.urlTypeIndex(name);
435         boolean isURL = (iurl >= 0);
436         String post = null;
437         if (isURL && (iurl = name.indexOf("?POST?")) >= 0) {
438           post = name.substring(iurl + 6);
439           name = name.substring(0, iurl);
440         }
441         boolean isApplet = (appletDocumentBaseURL != null);
442         if (isApplet || isURL) {
443           if (isApplet && isURL && appletProxy != null)
444             name = appletProxy + "?url=" + urlEncode(name);
445           URL url = (isApplet ? new URL(appletDocumentBaseURL, name, null)
446               : new URL((URL) null, name, null));
447           if (checkOnly)
448             return null;
449           name = url.toString();
450           if (showMsg && name.toLowerCase().indexOf("password") < 0)
451             Logger.info("FileManager opening url " + name);
452           // note that in the case of JS, this is a javajs.util.SB.
453           ret = vwr.apiPlatform.getURLContents(url, outputBytes, post, false);
454           //          if ((ret instanceof SB && ((SB) ret).length() < 3
455           //                || ret instanceof String && ((String) ret).startsWith("java."))
456           //              && name.startsWith("http://ves-hx-89.ebi.ac.uk")) {
457           //            // temporary bypass for EBI firewalled development server
458           //            // defaulting to current directory and JSON file
459           //            name = "http://chemapps.stolaf.edu/jmol/jsmol/data/"
460           //            + name.substring(name.lastIndexOf("/") + 1)
461           //            + (name.indexOf("/val") >= 0 ? ".val" : ".ann") + ".json";
462           //            ret = getBufferedInputStreamOrErrorMessageFromName(name, fullName,
463           //                showMsg, checkOnly, outputBytes, allowReader, allowCached);
464           //          }
465 
466           byte[] bytes = null;
467           if (ret instanceof SB) {
468             SB sb = (SB) ret;
469             if (allowReader && !Rdr.isBase64(sb))
470               return Rdr.getBR(sb.toString());
471             bytes = Rdr.getBytesFromSB(sb);
472           } else if (AU.isAB(ret)) {
473             bytes = (byte[]) ret;
474           }
475           if (bytes != null)
476             ret = Rdr.getBIS(bytes);
477         } else if (!allowCached
478             || (cacheBytes = (byte[]) cacheGet(name, true)) == null) {
479           if (showMsg)
480             Logger.info("FileManager opening file " + name);
481           ret = vwr.apiPlatform.getBufferedFileInputStream(name);
482         }
483         if (ret instanceof String)
484           return ret;
485       }
486       bis = (cacheBytes == null ? (BufferedInputStream) ret : Rdr
487           .getBIS(cacheBytes));
488       if (checkOnly) {
489         bis.close();
490         bis = null;
491       }
492       return bis;
493     } catch (Exception e) {
494       try {
495         if (bis != null)
496           bis.close();
497       } catch (IOException e1) {
498       }
499       errorMessage = "" + e;
500     }
501     return errorMessage;
502   }
503 
504   @SuppressWarnings("null")
getBufferedReaderForResource(Viewer vwr, Object resourceClass, String classPath, String resourceName)505   public static BufferedReader getBufferedReaderForResource(Viewer vwr,
506                                                             Object resourceClass,
507                                                             String classPath,
508                                                             String resourceName)
509       throws IOException {
510 
511     URL url;
512     /**
513      * @j2sNative
514      *
515      */
516     {
517       url = resourceClass.getClass().getResource(resourceName);
518       if (url == null) {
519         System.err.println("Couldn't find file: " + classPath + resourceName);
520         throw new IOException();
521       }
522       if (vwr == null || !vwr.async)
523         return Rdr.getBufferedReader(
524             new BufferedInputStream((InputStream) url.getContent()), null);
525     }
526     // applet only
527     resourceName = (url == null
528         ? vwr.vwrOptions.get("codePath") + classPath + resourceName
529             : url.getFile());
530     if (vwr.async) {
531       // if we are running asynchronously, this will be a problem.
532       Object bytes = vwr.fm.cacheGet(resourceName, false);
533       if (bytes == null)
534         throw new JmolAsyncException(resourceName);
535       return Rdr.getBufferedReader(Rdr.getBIS((byte[]) bytes), null);
536     }
537     // JavaScript only; here and not in JavaDoc to preserve Eclipse search reference
538     return (BufferedReader) vwr.fm.getBufferedReaderOrErrorMessageFromName(
539         resourceName, new String[] { null, null }, false, true);
540   }
541 
urlEncode(String name)542   private String urlEncode(String name) {
543     try {
544       return URLEncoder.encode(name, "utf-8");
545     } catch (UnsupportedEncodingException e) {
546       return name;
547     }
548   }
549 
550   /**
551    * just check for a file as being readable. Do not go into a zip file
552    *
553    * @param filename
554    * @param getStream
555    * @param ret
556    * @return String[2] where [0] is fullpathname and [1] is error message or null
557    */
getFullPathNameOrError(String filename, boolean getStream, String[] ret)558   Object getFullPathNameOrError(String filename, boolean getStream, String[] ret) {
559     String[] names = getClassifiedName(JC.fixProtocol(filename), true);
560     if (names == null || names[0] == null || names.length < 2)
561       return new String[] { null, "cannot read file name: " + filename };
562     String name = names[0];
563     String fullPath = fixDOSName(names[0]);
564     name = Rdr.getZipRoot(name);
565     Object errMsg = getBufferedInputStreamOrErrorMessageFromName(name, fullPath, false, !getStream, null, false, !getStream);
566     ret[0] = fullPath;
567     if (errMsg instanceof String)
568       ret[1] = (String) errMsg;
569     return errMsg;
570   }
571 
getBufferedReaderOrErrorMessageFromName(String name, String[] fullPathNameReturn, boolean isBinary, boolean doSpecialLoad)572   public Object getBufferedReaderOrErrorMessageFromName(String name,
573                                                  String[] fullPathNameReturn,
574                                                  boolean isBinary,
575                                                  boolean doSpecialLoad) {
576     name = JC.fixProtocol(name);
577     Object data = cacheGet(name, false);
578     boolean isBytes = AU.isAB(data);
579     byte[] bytes = (isBytes ? (byte[]) data : null);
580     if (name.startsWith("cache://")) {
581       if (data == null)
582         return "cannot read " + name;
583       if (isBytes) {
584         bytes = (byte[]) data;
585       } else {
586         return Rdr.getBR((String) data);
587       }
588     }
589     String[] names = getClassifiedName(name, true);
590     if (names == null)
591       return "cannot read file name: " + name;
592     if (fullPathNameReturn != null)
593       fullPathNameReturn[0] = fixDOSName(names[0]);
594     return getUnzippedReaderOrStreamFromName(names[0], bytes,
595         false, isBinary, false, doSpecialLoad, null);
596   }
597 
598   /**
599    *
600    * @param name
601    * @param bytesOrStream
602    *        cached bytes or a BufferedInputStream
603    * @param allowZipStream
604    *        if the file is a zip file, allow a return that is a ZipInputStream
605    * @param forceInputStream
606    *        always return a raw BufferedInputStream, not a BufferedReader, and
607    *        do not process PNGJ files
608    * @param isTypeCheckOnly
609    *        when possibly reading a spartan file for content (doSpecialLoad ==
610    *        true), just return the compound document's file list
611    * @param doSpecialLoad
612    *        check for a Spartan file
613    * @param htParams
614    * @return String if error or String[] if a type check or BufferedReader or
615    *         BufferedInputStream
616    */
617   @SuppressWarnings("resource")
getUnzippedReaderOrStreamFromName(String name, Object bytesOrStream, boolean allowZipStream, boolean forceInputStream, boolean isTypeCheckOnly, boolean doSpecialLoad, Map<String, Object> htParams)618   public Object getUnzippedReaderOrStreamFromName(String name,
619                                                   Object bytesOrStream,
620                                                   boolean allowZipStream,
621                                                   boolean forceInputStream,
622                                                   boolean isTypeCheckOnly,
623                                                   boolean doSpecialLoad,
624                                                   Map<String, Object> htParams) {
625     if (doSpecialLoad && bytesOrStream == null) {
626       Object o = (name.endsWith(".spt") ? new String[] { null, null, null } // DO NOT actually load any file
627           : name.indexOf(".spardir") < 0 ? null
628           : spartanUtil().getFileList(name, isTypeCheckOnly));
629       if (o != null)
630         return o;
631     }
632     name = JC.fixProtocol(name);
633     if (bytesOrStream == null
634         && (bytesOrStream = getCachedPngjBytes(name)) != null
635         && htParams != null)
636       htParams.put("sourcePNGJ", Boolean.TRUE);
637     name = name.replace("#_DOCACHE_", "");
638     String fullName = name;
639     String[] subFileList = null;
640     if (name.indexOf("|") >= 0) {
641       subFileList = PT.split(name.replace('\\', '/'), "|");
642       if (bytesOrStream == null)
643         Logger.info("FileManager opening zip " + name);
644       name = subFileList[0];
645     }
646     Object t = (bytesOrStream == null ? getBufferedInputStreamOrErrorMessageFromName(
647         name, fullName, true, false, null, !forceInputStream, true) : AU
648         .isAB(bytesOrStream) ? Rdr.getBIS((byte[]) bytesOrStream)
649         : (BufferedInputStream) bytesOrStream);
650     try {
651       if (t instanceof String || t instanceof BufferedReader)
652         return t;
653       BufferedInputStream bis = (BufferedInputStream) t;
654       if (Rdr.isGzipS(bis))
655         bis = Rdr.getUnzippedInputStream(vwr.getJzt(), bis);
656       // if we have a subFileList, we don't want to return the stream for the zip file itself
657       else if (Rdr.isBZip2S(bis))
658         bis = Rdr.getUnzippedInputStreamBZip2(vwr.getJzt(), bis);
659       // if we have a subFileList, we don't want to return the stream for the zip file itself
660       if (Rdr.isTar(bis)) {
661         Object o = vwr.getJzt().getZipFileDirectory(bis, subFileList, 1,
662             forceInputStream);
663         return (o instanceof String ? Rdr.getBR((String) o) : o);
664       }
665 
666       if (forceInputStream && subFileList == null)
667         return bis;
668       if (Rdr.isCompoundDocumentS(bis)) {
669         // very specialized reader; assuming we have a Spartan document here
670         CompoundDocument doc = (CompoundDocument) Interface.getInterface(
671             "javajs.util.CompoundDocument", vwr, "file");
672         doc.setDocStream(vwr.getJzt(), bis);
673         String s = doc.getAllDataFiles("Molecule", "Input").toString();
674         return (forceInputStream ? Rdr.getBIS(s.getBytes()) : Rdr.getBR(s));
675       }
676       // check for PyMOL or MMTF
677       if (Rdr.isMessagePackS(bis) || Rdr.isPickleS(bis))
678         return bis;
679       bis = Rdr.getPngZipStream(bis, true);
680       if (Rdr.isZipS(bis)) {
681         if (allowZipStream)
682           return vwr.getJzt().newZipInputStream(bis);
683         Object o = vwr.getJzt().getZipFileDirectory(bis, subFileList, 1,
684             forceInputStream);
685         return (o instanceof String ? Rdr.getBR((String) o) : o);
686       }
687       return (forceInputStream ? bis : Rdr.getBufferedReader(bis, null));
688     } catch (Exception ioe) {
689       return ioe.toString();
690     }
691   }
692 
693 
694   /**
695    *
696    * @param fileName
697    * @param addManifest
698    * @param allowCached
699    * @return [] if not a zip file;
700    */
getZipDirectory(String fileName, boolean addManifest, boolean allowCached)701   public String[] getZipDirectory(String fileName, boolean addManifest, boolean allowCached) {
702     Object t = getBufferedInputStreamOrErrorMessageFromName(fileName, fileName,
703         false, false, null, false, allowCached);
704     return vwr.getJzt().getZipDirectoryAndClose((BufferedInputStream) t, addManifest ? "JmolManifest" : null);
705   }
706 
getFileAsBytes(String name, OC out)707   public Object getFileAsBytes(String name, OC out) {
708     // used by OutputManager.createZipSet"
709     // will be full path name
710     if (name == null)
711       return null;
712     String fullName = name;
713     String[] subFileList = null;
714     if (name.indexOf("|") >= 0) {
715       subFileList = PT.split(name, "|");
716       name = subFileList[0];
717     }
718     // JavaScript will use this method to load a cached file,
719     // but in that case we can get the bytes directly and not
720     // fool with a BufferedInputStream, and we certainly do not want to
721     // open it twice in the case of the returned interior file being another PNGJ file
722     Object bytes = (subFileList != null ? null : getPngjOrDroppedBytes(fullName, name));
723     if (bytes == null) {
724       Object t = getBufferedInputStreamOrErrorMessageFromName(name, fullName,
725           false, false, null, false, true);
726       if (t instanceof String)
727         return "Error:" + t;
728       try {
729         BufferedInputStream bis = (BufferedInputStream) t;
730         bytes = (out != null || subFileList == null || subFileList.length <= 1
731             || !Rdr.isZipS(bis) && !Rdr.isPngZipStream(bis)
732              && !Rdr.isTar(bis)
733             ? Rdr.getStreamAsBytes(bis, out)
734                 : vwr.getJzt().getZipFileContentsAsBytes(bis, subFileList, 1));
735 
736         bis.close();
737       } catch (Exception ioe) {
738         return ioe.toString();
739       }
740     }
741     if (out == null || !AU.isAB(bytes))
742       return bytes;
743       out.write((byte[]) bytes, 0, -1);
744     return ((byte[]) bytes).length + " bytes";
745   }
746 
getFileAsMap(String name, String type)747   public Map<String, Object> getFileAsMap(String name, String type) {
748     Map<String, Object> bdata = new Hashtable<String, Object>();
749     Object t;
750     if (name == null) {
751       // return the current state as a PNGJ or ZIPDATA
752       String[] errMsg = new String[1];
753       byte[] bytes = vwr.getImageAsBytes(type, -1, -1, -1, errMsg);
754       if (errMsg[0] != null) {
755         bdata.put("_ERROR_", errMsg[0]);
756         return bdata;
757       }
758       t = Rdr.getBIS(bytes);
759     } else {
760       String[] data = new String[2];
761       t = getFullPathNameOrError(name, true, data);
762       if (t instanceof String) {
763         bdata.put("_ERROR_", t);
764         return bdata;
765       }
766       if (!checkSecurity(data[0])) {
767         bdata.put("_ERROR_", "java.io. Security exception: cannot read file "
768             + data[0]);
769         return bdata;
770       }
771     }
772     try {
773       vwr.getJzt().readFileAsMap((BufferedInputStream) t, bdata, name);
774 
775     } catch (Exception e) {
776       bdata.clear();
777       bdata.put("_ERROR_", "" + e);
778     }
779     return bdata;
780   }
781 
782   /**
783    *
784    * @param data
785    *        [0] initially path name, but returned as full path name; [1]file
786    *        contents (directory listing for a ZIP/JAR file) or error string
787    * @param nBytesMax
788    *        or -1
789    * @param doSpecialLoad
790    * @param allowBinary
791    * @param checkProtected
792    *        TODO
793    * @return true if successful; false on error
794    */
795 
getFileDataAsString(String[] data, int nBytesMax, boolean doSpecialLoad, boolean allowBinary, boolean checkProtected)796   public boolean getFileDataAsString(String[] data, int nBytesMax,
797                                      boolean doSpecialLoad,
798                                      boolean allowBinary, boolean checkProtected) {
799     data[1] = "";
800     String name = data[0];
801     if (name == null)
802       return false;
803     Object t = getBufferedReaderOrErrorMessageFromName(name, data, false,
804         doSpecialLoad);
805     if (t instanceof String) {
806       data[1] = (String) t;
807       return false;
808     }
809     if (checkProtected && !checkSecurity(data[0])) {
810       data[1] = "java.io. Security exception: cannot read file " + data[0];
811       return false;
812     }
813     try {
814       return Rdr.readAllAsString((BufferedReader) t, nBytesMax, allowBinary,
815           data, 1);
816     } catch (Exception e) {
817       return false;
818     }
819   }
820 
checkSecurity(String f)821   private boolean checkSecurity(String f) {
822     // when load() function and local file:
823     if (!f.startsWith("file:"))
824        return true;
825     int pt = f.lastIndexOf('/');
826     // root directory C:/foo or file:///c:/foo or "/foo"
827     // no hidden files
828     // no files without extension
829     if (f.lastIndexOf(":/") == pt - 1
830       || f.indexOf("/.") >= 0
831       || f.lastIndexOf('.') < f.lastIndexOf('/'))
832       return false;
833     return true;
834   }
835 
836   /**
837    * Load an image
838    * @param nameOrBytes
839    * @param echoName
840    * @param forceSync TODO
841    * @return true if asynchronous
842    */
843   @SuppressWarnings("unchecked")
loadImage(Object nameOrBytes, String echoName, boolean forceSync)844   public boolean loadImage(Object nameOrBytes, String echoName, boolean forceSync) {
845     Object image = null;
846     String nameOrError = null;
847     byte[] bytes = null;
848     boolean isPopupImage = (echoName != null && echoName.startsWith("\1"));
849     if (isPopupImage) {
850       if (echoName.equals("\1closeall\1null"))
851         return vwr.loadImageData(Boolean.TRUE, "\1closeall", "\1closeall", null);
852       if ("\1close".equals(nameOrBytes))
853         return vwr.loadImageData(Boolean.FALSE, "\1close", echoName, null);
854     }
855     if (nameOrBytes instanceof Map) {
856       nameOrBytes = (((Map<String, Object>) nameOrBytes).containsKey("_DATA_") ? ((Map<String, Object>) nameOrBytes)
857           .get("_DATA_") : ((Map<String, Object>) nameOrBytes).get("_IMAGE_"));
858     }
859     if (nameOrBytes instanceof SV)
860       nameOrBytes = ((SV) nameOrBytes).value;
861     String name = (nameOrBytes instanceof String ? (String) nameOrBytes : null);
862     boolean isAsynchronous = false;
863     if (name != null && name.startsWith(";base64,")) {
864       bytes = Base64.decodeBase64(name);
865     } else if (nameOrBytes instanceof BArray) {
866       bytes = ((BArray) nameOrBytes).data;
867     } else if (echoName == null || nameOrBytes instanceof String) {
868       String[] names = getClassifiedName((String) nameOrBytes, true);
869       nameOrError = (names == null ? "cannot read file name: " + nameOrBytes
870           : fixDOSName(names[0]));
871       if (names != null)
872         image = getImage(nameOrError, echoName, forceSync);
873       isAsynchronous = (image == null);
874     } else {
875       image = nameOrBytes;
876     }
877     if (bytes != null) {
878       image = getImage(bytes, echoName, true);
879       isAsynchronous = false;
880     }
881     if (image instanceof String) {
882       nameOrError = (String) image;
883       image = null;
884     }
885     if (!Viewer.isJS && image != null && bytes != null)
886       nameOrError = ";base64," + Base64.getBase64(bytes).toString();
887     if (!Viewer.isJS || isPopupImage && nameOrError == null
888         || !isPopupImage && image != null)
889       return vwr.loadImageData(image, nameOrError, echoName, null);
890     return isAsynchronous;
891     // JSmol will call that from awtjs2d.Platform.java asynchronously
892   }
893 
getImage(Object nameOrBytes, String echoName, boolean forceSync)894   public Object getImage(Object nameOrBytes, String echoName,
895                          boolean forceSync) {
896    return getJzu().getImage(vwr, nameOrBytes, echoName, forceSync);
897  }
898 
899   /**
900    * [0] and [2] may return same as [1] in the
901    * case of a local unsigned applet.
902    *
903    * @param name
904    * @param isFullLoad
905    *        false only when just checking path
906    * @return [0] full path name, [1] file name without path, [2] full URL
907    */
getClassifiedName(String name, boolean isFullLoad)908   private String[] getClassifiedName(String name, boolean isFullLoad) {
909     if (name == null)
910       return new String[] { null };
911     boolean doSetPathForAllFiles = (pathForAllFiles.length() > 0);
912     if (name.startsWith("?") || name.startsWith("http://?")|| name.startsWith("https://?")) {
913       if (!Viewer.isJS && (name = vwr.dialogAsk("Load", name, null)) == null)
914         return new String[] { isFullLoad ? "#CANCELED#" : null };
915       doSetPathForAllFiles = false;
916     }
917     GenericFileInterface file = null;
918     URL url = null;
919     String[] names = null;
920     if (name.startsWith("cache://")) {
921       names = new String[3];
922       names[0] = names[2] = name;
923       names[1] = stripPath(names[0]);
924       return names;
925     }
926     name = vwr.resolveDatabaseFormat(name);
927     if (name.indexOf(":") < 0 && name.indexOf("/") != 0)
928       name = addDirectory(vwr.getDefaultDirectory(), name);
929     if (appletDocumentBaseURL == null) {
930       // This code is for the app or signed local applet
931       // -- no local file reading for headless
932       if (OC.urlTypeIndex(name) >= 0 || vwr.haveAccess(ACCESS.NONE)
933           || vwr.haveAccess(ACCESS.READSPT) && !name.endsWith(".spt")
934           && !name.endsWith("/")) {
935         try {
936           url = new URL((URL) null, name, null);
937         } catch (MalformedURLException e) {
938           return new String[] { isFullLoad ? e.toString() : null };
939         }
940       } else {
941         file = vwr.apiPlatform.newFile(name);
942         String s = file.getFullPath();
943         // local unsigned applet may have access control issue here and get a null return
944         String fname = file.getName();
945         names = new String[] { (s == null ? fname : s), fname,
946             (s == null ? fname : "file:/" + s.replace('\\', '/')) };
947       }
948     } else {
949       // This code is only for the non-local applet
950       try {
951         if (name.indexOf(":\\") == 1 || name.indexOf(":/") == 1)
952           name = "file:/" + name;
953         //        else if (name.indexOf("/") == 0 && vwr.isSignedApplet())
954         //        name = "file:" + name;
955         url = new URL(appletDocumentBaseURL, name, null);
956       } catch (MalformedURLException e) {
957         return new String[] { isFullLoad ? e.toString() : null };
958       }
959     }
960     if (url != null) {
961       names = new String[3];
962       names[0] = names[2] = url.toString();
963       names[1] = stripPath(names[0]);
964     }
965     if (doSetPathForAllFiles) {
966       String name0 = names[0];
967       names[0] = pathForAllFiles + names[1];
968       Logger.info("FileManager substituting " + name0 + " --> " + names[0]);
969     }
970     if (isFullLoad && OC.isLocal(names[0])) {
971       String path = names[0];
972       if (file == null)
973         path = PT.trim(names[0].substring(names[0].indexOf(":") + 1), "/");
974       int pt = path.length() - names[1].length() - 1;
975       if (pt > 0) {
976         path = path.substring(0, pt);
977         setLocalPath(vwr, path, true);
978       }
979     }
980     return names;
981   }
982 
983   ///// DIRECTORY BUSINESS
984 
addDirectory(String defaultDirectory, String name)985   private static String addDirectory(String defaultDirectory, String name) {
986     if (defaultDirectory.length() == 0)
987       return name;
988     char ch = (name.length() > 0 ? name.charAt(0) : ' ');
989     String s = defaultDirectory.toLowerCase();
990     if ((s.endsWith(".zip") || s.endsWith(".tar")) && ch != '|' && ch != '/')
991       defaultDirectory += "|";
992     return defaultDirectory
993         + (ch == '/'
994             || ch == '/'
995             || (ch = defaultDirectory.charAt(defaultDirectory.length() - 1)) == '|'
996             || ch == '/' ? "" : "/") + name;
997   }
998 
getDefaultDirectory(String name)999   String getDefaultDirectory(String name) {
1000     String[] names = getClassifiedName(name, true);
1001     if (names == null)
1002       return "";
1003     name = fixPath(names[0]);
1004     return (name == null ? "" : name.substring(0, name.lastIndexOf("/")));
1005   }
1006 
fixPath(String path)1007   private static String fixPath(String path) {
1008     path = fixDOSName(path);
1009     path = PT.rep(path, "/./", "/");
1010     int pt = path.lastIndexOf("//") + 1;
1011     if (pt < 1)
1012       pt = path.indexOf(":/") + 1;
1013     if (pt < 1)
1014       pt = path.indexOf("/");
1015     if (pt < 0)
1016       return null;
1017     String protocol = path.substring(0, pt);
1018     path = path.substring(pt);
1019 
1020     while ((pt = path.lastIndexOf("/../")) >= 0) {
1021       int pt0 = path.substring(0, pt).lastIndexOf("/");
1022       if (pt0 < 0)
1023         return PT.rep(protocol + path, "/../", "/");
1024       path = path.substring(0, pt0) + path.substring(pt + 3);
1025     }
1026     if (path.length() == 0)
1027       path = "/";
1028     return protocol + path;
1029   }
1030 
getFilePath(String name, boolean addUrlPrefix, boolean asShortName)1031   public String getFilePath(String name, boolean addUrlPrefix,
1032                             boolean asShortName) {
1033     String[] names = getClassifiedName(name, false);
1034     return (names == null || names.length == 1 ? "" : asShortName ? names[1]
1035         : addUrlPrefix ? names[2]
1036         : names[0] == null ? ""
1037         : fixDOSName(names[0]));
1038   }
1039 
getLocalDirectory(Viewer vwr, boolean forDialog)1040   public static GenericFileInterface getLocalDirectory(Viewer vwr, boolean forDialog) {
1041     String localDir = (String) vwr
1042         .getP(forDialog ? "currentLocalPath" : "defaultDirectoryLocal");
1043     if (forDialog && localDir.length() == 0)
1044       localDir = (String) vwr.getP("defaultDirectoryLocal");
1045     if (localDir.length() == 0)
1046       return (vwr.isApplet ? null : vwr.apiPlatform.newFile(System
1047           .getProperty("user.dir", ".")));
1048     if (vwr.isApplet && localDir.indexOf("file:/") == 0)
1049       localDir = localDir.substring(6);
1050     GenericFileInterface f = vwr.apiPlatform.newFile(localDir);
1051     try {
1052       return f.isDirectory() ? f : f.getParentAsFile();
1053     } catch (Exception e) {
1054       return  null;
1055     }
1056   }
1057 
1058   /**
1059    * called by getImageFileNameFromDialog
1060    * called by getOpenFileNameFromDialog
1061    * called by getSaveFileNameFromDialog
1062    *
1063    * called by classifyName for any full file load
1064    * called from the CD command
1065    *
1066    * currentLocalPath is set in all cases
1067    *   and is used specifically for dialogs as a first try
1068    * defaultDirectoryLocal is set only when not from a dialog
1069    *   and is used only in getLocalPathForWritingFile or
1070    *   from an open/save dialog.
1071    * In this way, saving a file from a dialog doesn't change
1072    *   the "CD" directory.
1073    * Neither of these is saved in the state, but
1074    *
1075    *
1076    * @param vwr
1077    * @param path
1078    * @param forDialog
1079    */
setLocalPath(Viewer vwr, String path, boolean forDialog)1080   public static void setLocalPath(Viewer vwr, String path,
1081                                   boolean forDialog) {
1082     while (path.endsWith("/") || path.endsWith("\\"))
1083       path = path.substring(0, path.length() - 1);
1084     vwr.setStringProperty("currentLocalPath", path);
1085     if (!forDialog)
1086       vwr.setStringProperty("defaultDirectoryLocal", path);
1087   }
1088 
getLocalPathForWritingFile(Viewer vwr, String file)1089   public static String getLocalPathForWritingFile(Viewer vwr, String file) {
1090     if (file.startsWith("http://") || file.startsWith("https://"))
1091       return file;
1092     file = PT.rep(file, "?", "");
1093     if (file.indexOf("file:/") == 0)
1094       return file.substring(6);
1095     if (file.indexOf("/") == 0 || file.indexOf(":") >= 0)
1096       return file;
1097     GenericFileInterface dir = null;
1098     try {
1099       dir = getLocalDirectory(vwr, false);
1100     } catch (Exception e) {
1101       // access control for unsigned applet
1102     }
1103     return (dir == null ? file : fixPath(dir.toString() + "/" + file));
1104   }
1105 
1106   /**
1107    * Switch \ for / only for DOS names such as C:\temp\t.xyz, not names like http://cactus.nci.nih.gov/chemical/structure/CC/C=C\CC
1108    * @param fileName
1109    * @return fixed name
1110    */
fixDOSName(String fileName)1111   public static String fixDOSName(String fileName) {
1112     return (fileName.indexOf(":\\") >= 0 ? fileName.replace('\\', '/') : fileName);
1113   }
1114 
stripPath(String name)1115   public static String stripPath(String name) {
1116     int pt = Math.max(name.lastIndexOf("|"), name.lastIndexOf("/"));
1117     return name.substring(pt + 1);
1118   }
1119 
1120   ///// FILE TYPING /////
1121 
1122   private final static String DELPHI_BINARY_MAGIC_NUMBER = "\24\0\0\0"; //0x14 0 0 0 == "20-byte character string follows"
1123   public final static String PMESH_BINARY_MAGIC_NUMBER = "PM\1\0";
1124   public static final String JPEG_CONTINUE_STRING = " #Jmol...\0";
1125 
isScriptType(String fname)1126   public static boolean isScriptType(String fname) {
1127     return PT.isOneOf(fname.toLowerCase().substring(fname.lastIndexOf(".")+1), ";pse;spt;png;pngj;jmol;zip;");
1128   }
1129 
1130 // was simplistic check from FileDropper
1131 //  public static boolean isSurfaceType(String fname) {
1132 //    return PT.isOneOf(fname.toLowerCase().substring(fname.lastIndexOf(".")+1), ";jvxl;kin;o;msms;map;pmesh;mrc;efvet;cube;obj;dssr;bcif;");
1133 //  }
1134 //
determineSurfaceFileType(BufferedReader bufferedReader)1135   public static String determineSurfaceFileType(BufferedReader bufferedReader) {
1136     // drag-drop and isosurface command only
1137     // JVXL should be on the FIRST line of the file, but it may be
1138     // after comments or missing.
1139 
1140     // Apbs, Jvxl, or Cube, also efvet and DHBD
1141 
1142     String line = null;
1143 
1144     if (bufferedReader instanceof Rdr.StreamReader) {
1145       BufferedInputStream is = ((Rdr.StreamReader) bufferedReader).getStream();
1146       if (is.markSupported()) {
1147         try {
1148           is.mark(300);
1149           byte[] buf = new byte[300];
1150           is.read(buf, 0, 300);
1151           is.reset();
1152           if ((buf[0] & 0xFF) == 0x83) // Finite map(3)
1153             return "BCifDensity";
1154           if (buf[0] == 'P' && buf[1] == 'M' && buf[2] == 1 && buf[3] == 0)//          "PM\1\0"
1155             return "Pmesh";
1156           if (buf[208] == 'M' && buf[209] == 'A' && buf[210] == 'P')//          "MAP" at 208
1157             return "Mrc";
1158           if (buf[0] == '\24' && buf[1] == 0 && buf[2] == 0 && buf[3] == 0)
1159             return "DelPhi";
1160           if (buf[36] == 0 && buf[37] == 100) // header19 (short)100
1161             return "Dsn6";
1162         } catch (IOException e) {
1163           // ignore
1164         }
1165       }
1166     }
1167 
1168     LimitedLineReader br = null;
1169 
1170     try {
1171       br = new LimitedLineReader(bufferedReader, 16000);
1172       line = br.getHeader(0);
1173     } catch (Exception e) {
1174       //
1175     }
1176     if (br == null || line == null || line.length() == 0)
1177       return null;
1178 
1179     // binary formats: problem here is that the buffered reader
1180     // may be translating byte sequences into unicode
1181     // and thus shifting the offset
1182     int pt0 = line.indexOf('\0');
1183     if (pt0 >= 0) {
1184       if (line.charAt(0) == 0x83) // Finite map(3)
1185         return "BCifDensity";
1186       if (line.indexOf(PMESH_BINARY_MAGIC_NUMBER) == 0)
1187         return "Pmesh";
1188       if (line.indexOf("MAP ") == 208)
1189         return "Mrc";
1190       if (line.indexOf(DELPHI_BINARY_MAGIC_NUMBER) == 0)
1191         return "DelPhi";
1192       if (line.length() > 37
1193           && (line.charAt(36) == 0 && line.charAt(37) == 100 || line.charAt(36) == 0
1194               && line.charAt(37) == 100)) {
1195         // header19 (short)100
1196         return "Dsn6";
1197       }
1198     }
1199 
1200     //for (int i = 0; i < 220; i++)
1201     //  System.out.print(" " + i + ":" + (0 + line.charAt(i)));
1202     //System.out.println("");
1203 
1204     switch (line.charAt(0)) {
1205     case '@':
1206       if (line.indexOf("@text") == 0)
1207         return "Kinemage";
1208       break;
1209     case '#':
1210       if (line.indexOf(".obj") >= 0)
1211         return "Obj"; // #file: pymol.obj
1212       if (line.indexOf("MSMS") >= 0)
1213         return "Msms";
1214       break;
1215     case '&':
1216       if (line.indexOf("&plot") == 0)
1217         return "Jaguar";
1218       break;
1219     case '\r':
1220     case '\n':
1221       if (line.indexOf("ZYX") >= 0)
1222         return "Xplor";
1223       break;
1224     }
1225     if (line.indexOf("Here is your gzipped map") >= 0)
1226       return "UPPSALA" + line;
1227     if (line.startsWith("data_SERVER"))
1228       return "CifDensity";
1229     if (line.startsWith("4MESHC"))
1230       return "Pmesh4";
1231     if (line.indexOf("! nspins") >= 0)
1232       return "CastepDensity";
1233     if (line.indexOf("<jvxl") >= 0 && line.indexOf("<?xml") >= 0)
1234       return "JvxlXml";
1235     if (line.indexOf("#JVXL+") >= 0)
1236       return "Jvxl+";
1237     if (line.indexOf("#JVXL") >= 0)
1238       return "Jvxl";
1239     if (line.indexOf("#JmolPmesh") >= 0)
1240       return "Pmesh";
1241     if (line.indexOf("#obj") >= 0)
1242       return "Obj";
1243     if (line.indexOf("#pmesh") >= 0)
1244       return "Obj";
1245     if (line.indexOf("<efvet ") >= 0)
1246       return "Efvet";
1247     if (line.indexOf("usemtl") >= 0)
1248       return "Obj";
1249     if (line.indexOf("# object with") == 0)
1250       return "Nff";
1251     if (line.indexOf("BEGIN_DATAGRID_3D") >= 0
1252         || line.indexOf("BEGIN_BANDGRID_3D") >= 0)
1253       return "Xsf";
1254     if (line.indexOf("tiles in x, y") >= 0)
1255       return "Ras3D";
1256     if (line.indexOf(" 0.00000e+00 0.00000e+00      0      0\n") >= 0)
1257       return "Uhbd"; // older APBS http://sourceforge.net/p/apbs/code/ci/9527462a39126fb6cd880924b3cc4880ec4b78a9/tree/src/mg/vgrid.c
1258 
1259     // Apbs, Jvxl, Obj, or Cube, maybe formatted Plt
1260 
1261     line = br.readLineWithNewline();
1262     if (line.indexOf("object 1 class gridpositions counts") == 0)
1263       return "Apbs";
1264 
1265     String[] tokens = PT.getTokens(line);
1266     String line2 = br.readLineWithNewline();// second line
1267     if (tokens.length == 2 && PT.parseInt(tokens[0]) == 3
1268         && PT.parseInt(tokens[1]) != Integer.MIN_VALUE) {
1269       tokens = PT.getTokens(line2);
1270       if (tokens.length == 3 && PT.parseInt(tokens[0]) != Integer.MIN_VALUE
1271           && PT.parseInt(tokens[1]) != Integer.MIN_VALUE
1272           && PT.parseInt(tokens[2]) != Integer.MIN_VALUE)
1273         return "PltFormatted";
1274     }
1275     String line3 = br.readLineWithNewline(); // third line
1276     if (line.startsWith("v ") && line2.startsWith("v ")
1277         && line3.startsWith("v "))
1278       return "Obj";
1279     //next line should be the atom line
1280     int nAtoms = PT.parseInt(line3);
1281     if (nAtoms == Integer.MIN_VALUE)
1282       return (line3.indexOf("+") == 0 ? "Jvxl+" : null);
1283     tokens = PT.getTokens(line3);
1284     if (tokens[0].indexOf(".") > 0)
1285       return (line3.length() >= 60 || tokens.length != 3 ? null : "VaspChgcar"); // M40 files are > 60 char
1286     if (nAtoms >= 0)
1287       return (tokens.length == 4 || tokens.length == 5 && tokens[4].equals("1") ? "Cube"
1288           : null); //Can't be a Jvxl file;
1289     nAtoms = -nAtoms;
1290     for (int i = 4 + nAtoms; --i >= 0;)
1291       if ((line = br.readLineWithNewline()) == null)
1292         return null;
1293     int nSurfaces = PT.parseInt(line);
1294     if (nSurfaces == Integer.MIN_VALUE)
1295       return null;
1296     return (nSurfaces < 0 ? "Jvxl" : "Cube"); //Final test looks at surface definition line
1297   }
1298 
1299   ///// JMOL SCRIPT FILE PROCESSING
1300 
1301   /**
1302    * check a JmolManifest for a reference to a script file (.spt)
1303    *
1304    * @param manifest
1305    * @return null, "", or a directory entry in the ZIP file
1306    */
1307 
getManifestScriptPath(String manifest)1308   public static String getManifestScriptPath(String manifest) {
1309     if (manifest.indexOf("$SCRIPT_PATH$") >= 0)
1310       return "";
1311     String ch = (manifest.indexOf('\n') >= 0 ? "\n" : "\r");
1312     if (manifest.indexOf(".spt") >= 0) {
1313       String[] s = PT.split(manifest, ch);
1314       for (int i = s.length; --i >= 0;)
1315         if (s[i].indexOf(".spt") >= 0)
1316           return "|" + PT.trim(s[i], "\r\n \t");
1317     }
1318     return null;
1319   }
1320 
getFileReferences(String script, Lst<String> fileList, Lst<String> fileListUTF)1321   public static void getFileReferences(String script, Lst<String> fileList,
1322                                        Lst<String> fileListUTF) {
1323     for (int ipt = 0; ipt < scriptFilePrefixes.length; ipt++) {
1324       String tag = scriptFilePrefixes[ipt];
1325       int i = -1;
1326       while ((i = script.indexOf(tag, i + 1)) >= 0) {
1327         String s = stripTypePrefix(PT.getQuotedStringAt(script, i));
1328         if (s.indexOf("\\u") >= 0)
1329           s = Escape.unescapeUnicode(s);
1330         fileList.addLast(s);
1331         if (fileListUTF != null) {
1332           if (s.indexOf("\\u") >= 0)
1333             s = Escape.unescapeUnicode(s);
1334           fileListUTF.addLast(s);
1335         }
1336       }
1337     }
1338   }
1339 
1340   private static String[] scriptFilePrefixes = new String[] { "/*file*/\"",
1341     "FILE0=\"", "FILE1=\"" };
1342 
setScriptFileReferences(String script, String localPath, String remotePath, String scriptPath)1343   public static String setScriptFileReferences(String script, String localPath,
1344                                                String remotePath,
1345                                                String scriptPath) {
1346     if (localPath != null)
1347       script = setScriptFileRefs(script, localPath, true);
1348     if (remotePath != null)
1349       script = setScriptFileRefs(script, remotePath, false);
1350     script = PT.rep(script, "\1\"", "\"");
1351     if (scriptPath != null) {
1352       while (scriptPath.endsWith("/"))
1353         scriptPath = scriptPath.substring(0, scriptPath.length() - 1);
1354       for (int ipt = 0; ipt < scriptFilePrefixes.length; ipt++) {
1355         String tag = scriptFilePrefixes[ipt];
1356         script = PT.rep(script, tag + ".", tag + scriptPath);
1357       }
1358     }
1359     return script;
1360   }
1361 
1362   /**
1363    * Sets all local file references in a script file to point to files within
1364    * dataPath. If a file reference contains dataPath, then the file reference is
1365    * left with that RELATIVE path. Otherwise, it is changed to a relative file
1366    * name within that dataPath.
1367    *
1368    * Only file references starting with "file://" are changed.
1369    *
1370    * @param script
1371    * @param dataPath
1372    * @param isLocal
1373    * @return revised script
1374    */
setScriptFileRefs(String script, String dataPath, boolean isLocal)1375   private static String setScriptFileRefs(String script, String dataPath,
1376                                                 boolean isLocal) {
1377     if (dataPath == null)
1378       return script;
1379     boolean noPath = (dataPath.length() == 0);
1380     Lst<String> fileNames = new  Lst<String>();
1381     getFileReferences(script, fileNames, null);
1382     Lst<String> oldFileNames = new  Lst<String>();
1383     Lst<String> newFileNames = new  Lst<String>();
1384     int nFiles = fileNames.size();
1385     for (int iFile = 0; iFile < nFiles; iFile++) {
1386       String name0 = fileNames.get(iFile);
1387       String name = name0;
1388       if (isLocal == OC.isLocal(name)) {
1389         int pt = (noPath ? -1 : name.indexOf("/" + dataPath + "/"));
1390         if (pt >= 0) {
1391           name = name.substring(pt + 1);
1392         } else {
1393           pt = name.lastIndexOf("/");
1394           if (pt < 0 && !noPath)
1395             name = "/" + name;
1396           if (pt < 0 || noPath)
1397             pt++;
1398           name = dataPath + name.substring(pt);
1399         }
1400       }
1401       Logger.info("FileManager substituting " + name0 + " --> " + name);
1402       oldFileNames.addLast("\"" + name0 + "\"");
1403       newFileNames.addLast("\1\"" + name + "\"");
1404     }
1405     return PT.replaceStrings(script, oldFileNames, newFileNames);
1406   }
1407 
1408   //// CACHING ////
1409 
1410   private Map<String, Object> cache = new Hashtable<String, Object>();
1411   public Map<String, Object> pngjCache;
1412   public Map<String, byte[]> spardirCache;
1413 
cachePut(String key, Object data)1414   void cachePut(String key, Object data) {
1415     key = fixDOSName(key);
1416     if (Logger.debugging)
1417       Logger.debug("cachePut " + key);
1418     if (data == null || "".equals(data)) { // J2S error -- cannot implement Int32Array.equals
1419       cache.remove(key);
1420       return;
1421     }
1422     cache.put(key, data);
1423     getCachedPngjBytes(key);
1424   }
1425 
cacheGet(String key, boolean bytesOnly)1426   public Object cacheGet(String key, boolean bytesOnly) {
1427     key = fixDOSName(key);
1428     // in the case of JavaScript local file reader,
1429     // this will be a cached file, and the filename will not be known.
1430     int pt = key.indexOf("|");
1431     if (pt >= 0 && !key.endsWith("##JmolSurfaceInfo##")) // check for PyMOL surface creation
1432       key = key.substring(0, pt);
1433     key = getFilePath(key, true, false);
1434     Object data = null;
1435     /**
1436      * @j2sNative
1437      *
1438      * (data = Jmol.Cache.get(key)) || (data = this.cache.get(key));
1439      *
1440      */
1441     {
1442     //if (Logger.debugging)
1443       //Logger.debug
1444        data = cache.get(key);
1445        if (data != null)
1446          Logger.info("cacheGet " + key);
1447     }
1448     return (bytesOnly && (data instanceof String) ? null : data);
1449   }
1450 
cacheClear()1451   void cacheClear() {
1452     Logger.info("cache cleared");
1453     cache.clear();
1454     //String fileName = null;
1455     //fileName = fileName == null ? null : getCanonicalName(Rdr.getZipRoot(fileName));
1456     if (pngjCache == null)// || fileName != null && !pngjCache.containsKey(fileName))
1457       return;
1458     pngjCache = null;
1459     Logger.info("PNGJ cache cleared");
1460   }
1461 
cacheFileByNameAdd(String fileName, boolean isAdd)1462   public int cacheFileByNameAdd(String fileName, boolean isAdd) {
1463     if (fileName == null || !isAdd && fileName.equalsIgnoreCase("")) {
1464       cacheClear();
1465       return -1;
1466     }
1467     Object data;
1468     if (isAdd) {
1469       fileName = JC.fixProtocol(vwr.resolveDatabaseFormat(fileName));
1470       data = getFileAsBytes(fileName, null);
1471       if (data instanceof String)
1472         return 0;
1473       cachePut(fileName, data);
1474     } else {
1475       if (fileName.endsWith("*"))
1476         return AU.removeMapKeys(cache, fileName.substring(0, fileName.length() - 1));
1477       data = cache.remove(fixDOSName(fileName));
1478     }
1479     return (data == null ? 0 : data instanceof String ? ((String) data).length()
1480         : ((byte[]) data).length);
1481   }
1482 
cacheList()1483   public Map<String, Integer> cacheList() {
1484     Map<String, Integer> map = new Hashtable<String, Integer>();
1485     for (Map.Entry<String, Object> entry : cache.entrySet())
1486       map.put(entry.getKey(), Integer
1487           .valueOf(AU.isAB(entry.getValue()) ? ((byte[]) entry
1488               .getValue()).length : entry.getValue().toString().length()));
1489     return map;
1490   }
1491 
getCanonicalName(String pathName)1492   public String getCanonicalName(String pathName) {
1493     String[] names = getClassifiedName(pathName, true);
1494     return (names == null ? pathName : names[2]);
1495   }
1496 
recachePngjBytes(String fileName, byte[] bytes)1497   public void recachePngjBytes(String fileName, byte[] bytes) {
1498     if (pngjCache == null || !pngjCache.containsKey(fileName))
1499       return;
1500     pngjCache.put(fileName, bytes);
1501     Logger.info("PNGJ recaching " + fileName + " (" + bytes.length + ")");
1502   }
1503 
getPngjOrDroppedBytes(String fullName, String name)1504   private byte[] getPngjOrDroppedBytes(String fullName, String name) {
1505     byte[] bytes = getCachedPngjBytes(fullName);
1506     return (bytes == null ? (byte[]) cacheGet(name, true) : bytes);
1507   }
1508 
getCachedPngjBytes(String pathName)1509   private byte[] getCachedPngjBytes(String pathName) {
1510     return (
1511         pathName == null || pngjCache == null || pathName.indexOf(".png") < 0 ? null
1512             : getJzu().getCachedPngjBytes(this, pathName));
1513   }
1514 
1515   //// BytePoster interface
1516 
1517   @Override
postByteArray(String fileName, byte[] bytes)1518   public String postByteArray(String fileName, byte[] bytes) {
1519     // BytePoster interface - for javajs.util.OC (output channel)
1520     // in principle, could have sftp or ftp here
1521     // but sftp is not implemented
1522     if (fileName.startsWith("cache://")) {
1523       cachePut(fileName, bytes);
1524       return "OK " + bytes.length + "cached";
1525     }
1526     Object ret = getBufferedInputStreamOrErrorMessageFromName(fileName, null, false,
1527             false, bytes, false, true);
1528     if (ret instanceof String)
1529       return (String) ret;
1530     try {
1531       ret = Rdr.getStreamAsBytes((BufferedInputStream) ret, null);
1532     } catch (IOException e) {
1533       try {
1534         ((BufferedInputStream) ret).close();
1535       } catch (IOException e1) {
1536         // ignore
1537       }
1538     }
1539     return (ret == null ? "" : Rdr.fixUTF((byte[]) ret));
1540   }
1541 
1542   /**
1543    * Check to see if this is a Jmol WRITE file type that might be or have attached a ZIP collection .
1544    *
1545    * @param type the
1546    * @return true if PNG, PNGJ, JMOL, ZIP, or ZIPALL
1547    */
isJmolType(String type)1548   public static boolean isJmolType(String type) {
1549     return  (type.equals("PNG") || type.equals("PNGJ") || type.equals("JMOL") || type.equals("ZIP") || type.equals("ZIPALL"));
1550   }
1551 
1552   /**
1553    * Check to see if it is possible that this file has been embedded by Jmol
1554    * using JC.EMBEDDED_SCRIPT_TAG. This includes all Jmol types, JPEG images, and
1555    * export types POV, POVRAY, and IDTF
1556    *
1557    * @param type raw extension or file name
1558    * @return true if this file might contain an embedded script
1559    */
isEmbeddable(String type)1560   public static boolean isEmbeddable(String type) {
1561     int pt = type.lastIndexOf('.');
1562     if (pt >= 0)
1563       type = type.substring(pt + 1);
1564     type = type.toUpperCase();
1565     // note - not JPG64 here. Presumes that is just for image creation in JavaScript
1566     return (isJmolType(type) || PT.isOneOf(type, ";JPG;JPEG;POV;IDTF;"));
1567   }
1568 
1569   /**
1570    * Get the specified SPT file of a Jmol zip collection or the embedded script
1571    * for any other file that is embeddable.
1572    *
1573    * @param fileName
1574    * @param allowCached
1575    * @param sptName
1576    *        state.spt, movie.spt, or null
1577    * @return embedded state.spt, movie.spt, or a script embedded using
1578    *         JC.EMBEDDED_SCRIPT_TAG, or "" if not found.
1579    */
getEmbeddedFileState(String fileName, boolean allowCached, String sptName)1580   public String getEmbeddedFileState(String fileName, boolean allowCached, String sptName) {
1581     if (!isEmbeddable(fileName))
1582       return "";
1583     String[] dir = getZipDirectory(fileName, false, allowCached);
1584     if (dir.length == 0) {
1585       String state = vwr.getFileAsString4(fileName, -1, false, true, false, "file");
1586       return (state.indexOf(JC.EMBEDDED_SCRIPT_TAG) < 0 ? ""
1587           : getEmbeddedScript(state));
1588     }
1589     for (int i = 0; i < dir.length; i++)
1590       if (dir[i].indexOf(sptName) >= 0) {
1591         String[] data = new String[] { fileName + "|" + dir[i], null };
1592         getFileDataAsString(data, -1, false, false, false);
1593         return data[1];
1594       }
1595     return "";
1596   }
1597 
1598   /**
1599    * Stip PDB::file://... from a file name
1600    *
1601    * @param fileName
1602    * @return  stripped name
1603    */
stripTypePrefix(String fileName)1604   public static String stripTypePrefix(String fileName) {
1605     int pt = fileName.indexOf("::");
1606     return (pt < 0 || pt >= 20 ? fileName : fileName.substring(pt + 2));
1607   }
1608 
1609   /**
1610    * Extract a Jmol script embedded using JC.EMBEDDED_SCRIPT_TAG.
1611    *
1612    * @param s
1613    * @return the embedded script or null
1614    */
getEmbeddedScript(String s)1615   public static String getEmbeddedScript(String s) {
1616     if (s == null)
1617       return s;
1618     int pt = s.indexOf(JC.EMBEDDED_SCRIPT_TAG);
1619     if (pt < 0)
1620       return s;
1621     int pt1 = s.lastIndexOf("/*", pt);
1622     int pt2 = s.indexOf((s.charAt(pt1 + 2) == '*' ? "*" : "") + "*/",
1623         pt);
1624     if (pt1 >= 0 && pt2 >= pt)
1625       s = s.substring(
1626           pt + JC.EMBEDDED_SCRIPT_TAG.length(), pt2)
1627           + "\n";
1628     while ((pt1 = s.indexOf(JPEG_CONTINUE_STRING)) >= 0)
1629       s = s.substring(0, pt1)
1630           + s.substring(pt1 + JPEG_CONTINUE_STRING.length() + 4);
1631     if (Logger.debugging)
1632       Logger.debug(s);
1633     return s;
1634   }
1635 
1636 }
1637