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