Jmol/SwingJS update. Includes new transpiler and runtime
[jalview.git] / srcjar / javajs / util / OC.java
1 package javajs.util;
2
3 import java.io.BufferedWriter;
4 import java.io.ByteArrayOutputStream;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.OutputStream;
8
9 import javajs.api.BytePoster;
10 import javajs.api.GenericOutputChannel;
11 import javajs.api.js.J2SObjectInterface;
12
13 /**
14  * 
15  * A generic output method. JmolOutputChannel can be used to:
16  * 
17  * add characters to a StringBuffer 
18  *   using fileName==null, append() and toString()
19  *   
20  * add bytes utilizing ByteArrayOutputStream 
21  *   using writeBytes(), writeByteAsInt(), append()*, and bytesAsArray()
22  *       *append() can be used as long as os==ByteArrayOutputStream
23  *        or it is not used before one of the writeByte methods. 
24  * 
25  * output characters to a FileOutputStream 
26  *  using os==FileOutputStream, asWriter==true, append(), and closeChannel()
27  *  
28  * output bytes to a FileOutputStream 
29  *  using os==FileOutputStream, writeBytes(), writeByteAsInt(), append(), and closeChannel()
30  * 
31  * post characters or bytes to a remote server
32  *  using fileName=="http://..." or "https://...",
33  *    writeBytes(), writeByteAsInt(), append(), and closeChannel()
34  *    
35  * send characters or bytes to a JavaScript function
36  *  when JavaScript and (typeof fileName == "function")
37  *  
38  * if fileName equals ";base64,", then the data are base64-encoded
39  * prior to writing, and closeChannel() returns the data.
40  * 
41  *  @author hansonr  Bob Hanson hansonr@stolaf.edu  9/2013
42  *  
43  *  
44  */
45
46 public class OC extends OutputStream implements GenericOutputChannel {
47  
48   private BytePoster bytePoster; // only necessary for writing to http:// or https://
49   private String fileName;
50   private BufferedWriter bw;
51   private boolean isLocalFile;
52   private int byteCount;
53   private boolean isCanceled;
54   private boolean closed;
55   private OutputStream os;
56   private SB sb;
57   private String type;
58         private boolean isBase64;
59         private OutputStream os0;
60         private byte[] bytes; // preset bytes; output only
61   
62         public boolean bigEndian = true;
63
64         private boolean isTemp;
65         
66         /**
67          * Setting isTemp=true informs OC that this is a temporary file 
68          * and not to send it to the user as a "download". Instead, the calling
69          * class can use .toByteArray() to retrieve the byte[] result.
70          * 
71          * @param tf
72          */
73         public void setTemp(boolean tf) {
74                 isTemp = tf;
75         }
76
77
78   
79   @Override
80   public boolean isBigEndian() {
81     return bigEndian;
82   }
83
84   public void setBigEndian(boolean TF) {
85         bigEndian = TF;
86   }
87
88         /**
89          * Set up an output channel. String or byte data can be added without problem.
90          * 
91          * @param bytePoster a byte poster can take the output byte[] when closing and
92          *                   do something with them
93          * @param fileName   TODO: It is possible that JavaScript will call this with a
94          *                   function name for fileName
95          * @param asWriter   string-based
96          * @param os         the desired target OutputStream - not the calling stream!
97          * @return
98          */
99   public OC setParams(BytePoster bytePoster, String fileName,
100                                      boolean asWriter, OutputStream os) {
101     this.bytePoster = bytePoster;
102     isBase64 = ";base64,".equals(fileName);
103     if (isBase64) {
104         fileName = null;
105         os0 = os;
106         os = null;
107     }
108     this.fileName = fileName;
109     this.os = os;
110     isLocalFile = (fileName != null && !isRemote(fileName));
111     if (asWriter && !isBase64 && os != null) 
112         bw = Rdr.getBufferedWriter(os, null);
113     return this;
114   }
115
116   public OC setBytes(byte[] b) {
117         bytes = b;
118         return this;
119   }
120   
121   public String getFileName() {
122     return fileName;
123   }
124   
125   public String getName() {
126     return (fileName == null ? null : fileName.substring(fileName.lastIndexOf("/") + 1));
127   }
128
129   public int getByteCount() {
130     return byteCount;
131   }
132
133   /**
134    * 
135    * @param type  user-identified type (PNG, JPG, etc)
136    */
137   public void setType(String type) {
138     this.type = type;
139   }
140   
141   public String getType() {
142     return type;
143   }
144
145   /**
146    * will go to string buffer if bw == null and os == null
147    * 
148    * @param s
149    * @return this, for chaining like a standard StringBuffer
150    * 
151    */
152   public OC append(String s) {
153     try {
154       if (bw != null) {
155         bw.write(s);
156       } else if (os == null) {
157         if (sb == null)
158           sb = new SB();
159         sb.append(s);
160       } else {
161         byte[] b = s.getBytes();
162         os.write(b, 0, b.length);
163         byteCount += b.length;
164         return this;
165       }
166     } catch (IOException e) {
167       // ignore
168     }
169     byteCount += s.length(); // not necessarily exactly correct if unicode
170     return this;
171   }
172
173   @Override
174   public void reset() {
175     sb = null;
176     initOS();
177   }
178
179
180   private void initOS() {
181     if (sb != null) {
182       String s = sb.toString();
183       reset();
184       append(s);
185       return;
186     }
187     try {
188       /**
189        * @j2sNative
190        * 
191        *            this.os = null;
192        */
193       {
194         if (os instanceof FileOutputStream) {
195           os.close();
196           os = new FileOutputStream(fileName);
197         } else {
198           os = null;
199         }
200       }
201       if (os == null)
202         os = new ByteArrayOutputStream();
203       if (bw != null) {
204         bw.close();
205         bw = Rdr.getBufferedWriter(os, null);
206       }
207     } catch (Exception e) {
208       // not perfect here.
209       System.out.println(e.toString());
210     }
211     byteCount = 0;
212   }
213
214   /**
215    * @param b  
216    */
217   @Override
218   public void writeByteAsInt(int b) {
219     if (os == null)
220       initOS();
221     {
222       try {
223         os.write(b);
224       } catch (IOException e) {
225       }
226     }
227     byteCount++;
228   }
229   
230   @Override
231   public void write(byte[] buf, int i, int len) {
232     if (os == null)
233       initOS();
234     try {
235       os.write(buf, i, len);
236     } catch (IOException e) {
237     }
238     byteCount += len;
239   }
240   
241   @Override
242   public void writeShort(short i) {
243     if (isBigEndian()) {
244       writeByteAsInt(i >> 8);
245       writeByteAsInt(i);
246     } else {
247       writeByteAsInt(i);
248       writeByteAsInt(i >> 8);
249     }
250   }
251
252   @Override
253   public void writeLong(long b) {
254     if (isBigEndian()) {
255       writeInt((int) ((b >> 32) & 0xFFFFFFFFl));
256       writeInt((int) (b & 0xFFFFFFFFl));
257     } else {
258       writeByteAsInt((int) (b >> 56));
259       writeByteAsInt((int) (b >> 48));
260       writeByteAsInt((int) (b >> 40));
261       writeByteAsInt((int) (b >> 32));
262       writeByteAsInt((int) (b >> 24));
263       writeByteAsInt((int) (b >> 16));
264       writeByteAsInt((int) (b >> 8));
265       writeByteAsInt((int) b);
266     }
267   }
268
269   public void write(int b) {
270     // required by standard ZipOutputStream -- do not use, as it will break JavaScript methods
271     if (os == null)
272       initOS();
273     try {
274       os.write(b);
275     } catch (IOException e) {
276     }
277     byteCount++;
278   }
279
280   public void write(byte[] b) {
281     // not used in JavaScript due to overloading problem there
282     write(b, 0, b.length);
283   }
284
285   public void cancel() {
286     isCanceled = true;
287     closeChannel();
288   }
289
290   @Override
291   @SuppressWarnings({ "unused" })
292   public String closeChannel() {
293     if (closed)
294       return null;
295     // can't cancel file writers
296     try {
297       if (bw != null) {
298         bw.flush();
299         bw.close();
300       } else if (os != null) {
301         os.flush();
302         os.close();
303       }
304       if (os0 != null && isCanceled) {
305         os0.flush();
306         os0.close();
307       }
308     } catch (Exception e) {
309       // ignore closing issues
310     }
311     if (isCanceled) {
312       closed = true;
313       return null;
314     }
315     if (fileName == null) {
316       if (isBase64) {
317         String s = getBase64();
318         if (os0 != null) {
319           os = os0;
320           append(s);
321         }
322         sb = new SB();
323         sb.append(s);
324         isBase64 = false;
325         return closeChannel();
326       }
327       return (sb == null ? null : sb.toString());
328     }
329     closed = true;
330     J2SObjectInterface J2S = null;
331     Object _function = null;
332     /**
333      * @j2sNative
334      * 
335      *            J2S = self.J2S || self.Jmol; _function = (typeof this.fileName == "function" ?
336      *            this.fileName : null);
337      * 
338      */
339     {
340       if (!isLocalFile) {
341         String ret = postByteArray(); // unsigned applet could do this
342         if (ret.startsWith("java.net"))
343           byteCount = -1;
344         return ret;
345       }
346     }
347     if (J2S != null && !isTemp) {
348         
349       // action here generally will be for the browser to display a download message
350       // temp files will not be sent this way.
351       Object data = (sb == null ? toByteArray() : sb.toString());
352       if (_function == null)
353         J2S.doAjax(fileName, null, data, sb == null);
354       else
355         J2S.applyFunc(_function, data);
356     }
357     return null;
358   }
359
360         public boolean isBase64() {
361                 return isBase64;
362         }
363
364         public String getBase64() {
365     return Base64.getBase64(toByteArray()).toString();
366         }
367         
368   public byte[] toByteArray() {
369     return (bytes != null ? bytes : (bytes = os instanceof ByteArrayOutputStream ? ((ByteArrayOutputStream)os).toByteArray() : 
370         sb == null ? null : sb.toBytes(0, sb.length())));
371   }
372
373   @Override
374   @Deprecated
375   public void close() {
376     closeChannel();
377   }
378
379   @Override
380   public String toString() {
381     if (bw != null)
382       try {
383         bw.flush();
384       } catch (IOException e) {
385         // TODO
386       }
387     if (sb != null)
388       return closeChannel();
389     return byteCount + " bytes";
390   }
391
392   /**
393    * We have constructed some sort of byte[] that needs to be posted to a remote site.
394    * We don't do that posting here -- we let the bytePoster do it.
395    * 
396    * @return
397    */
398   private String postByteArray() {
399     return bytePoster == null ? null : bytePoster.postByteArray(fileName, toByteArray());
400   }
401
402   public final static String[] urlPrefixes = { "http:", "https:", "sftp:", "ftp:",
403   "file:" };
404   // note that SFTP is not supported
405   public final static int URL_LOCAL = 4;
406
407   public static boolean isRemote(String fileName) {
408     if (fileName == null)
409       return false;
410     int itype = urlTypeIndex(fileName);
411     return (itype >= 0 && itype != URL_LOCAL);
412   }
413
414   public static boolean isLocal(String fileName) {
415     if (fileName == null)
416       return false;
417     int itype = urlTypeIndex(fileName);
418     return (itype < 0 || itype == URL_LOCAL);
419   }
420
421   public static int urlTypeIndex(String name) {
422     if (name == null)
423       return -2; // local unsigned applet
424     for (int i = 0; i < urlPrefixes.length; ++i) {
425       if (name.startsWith(urlPrefixes[i])) {
426         return i;
427       }
428     }
429     return -1;
430   }
431
432   @Override
433   public void writeInt(int i) {
434     if (bigEndian) {
435       writeByteAsInt(i >> 24);
436       writeByteAsInt(i >> 16);
437       writeByteAsInt(i >> 8);
438       writeByteAsInt(i);
439     } else {
440       writeByteAsInt(i);
441       writeByteAsInt(i >> 8);
442       writeByteAsInt(i >> 16);
443       writeByteAsInt(i >> 24);
444     }
445   }
446
447   public void writeFloat(float x) {
448     writeInt(x == 0 ? 0 : Float.floatToIntBits(x));
449   }
450
451 }