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;
9 import javajs.api.BytePoster;
10 import javajs.api.GenericOutputChannel;
11 import javajs.api.js.J2SObjectInterface;
15 * A generic output method. JmolOutputChannel can be used to:
17 * add characters to a StringBuffer
18 * using fileName==null, append() and toString()
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.
25 * output characters to a FileOutputStream
26 * using os==FileOutputStream, asWriter==true, append(), and closeChannel()
28 * output bytes to a FileOutputStream
29 * using os==FileOutputStream, writeBytes(), writeByteAsInt(), append(), and closeChannel()
31 * post characters or bytes to a remote server
32 * using fileName=="http://..." or "https://...",
33 * writeBytes(), writeByteAsInt(), append(), and closeChannel()
35 * send characters or bytes to a JavaScript function
36 * when JavaScript and (typeof fileName == "function")
38 * if fileName equals ";base64,", then the data are base64-encoded
39 * prior to writing, and closeChannel() returns the data.
41 * @author hansonr Bob Hanson hansonr@stolaf.edu 9/2013
46 public class OC extends OutputStream implements GenericOutputChannel {
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;
58 private boolean isBase64;
59 private OutputStream os0;
60 private byte[] bytes; // preset bytes; output only
62 public boolean bigEndian = true;
64 private boolean isTemp;
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.
73 public void setTemp(boolean tf) {
80 public boolean isBigEndian() {
84 public void setBigEndian(boolean TF) {
89 * Set up an output channel. String or byte data can be added without problem.
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!
99 public OC setParams(BytePoster bytePoster, String fileName,
100 boolean asWriter, OutputStream os) {
101 this.bytePoster = bytePoster;
102 isBase64 = ";base64,".equals(fileName);
108 this.fileName = fileName;
110 isLocalFile = (fileName != null && !isRemote(fileName));
111 if (asWriter && !isBase64 && os != null)
112 bw = Rdr.getBufferedWriter(os, null);
116 public OC setBytes(byte[] b) {
121 public String getFileName() {
125 public String getName() {
126 return (fileName == null ? null : fileName.substring(fileName.lastIndexOf("/") + 1));
129 public int getByteCount() {
135 * @param type user-identified type (PNG, JPG, etc)
137 public void setType(String type) {
141 public String getType() {
146 * will go to string buffer if bw == null and os == null
149 * @return this, for chaining like a standard StringBuffer
152 public OC append(String s) {
156 } else if (os == null) {
161 byte[] b = s.getBytes();
162 os.write(b, 0, b.length);
163 byteCount += b.length;
166 } catch (IOException e) {
169 byteCount += s.length(); // not necessarily exactly correct if unicode
174 public void reset() {
180 private void initOS() {
182 String s = sb.toString();
194 if (os instanceof FileOutputStream) {
196 os = new FileOutputStream(fileName);
202 os = new ByteArrayOutputStream();
205 bw = Rdr.getBufferedWriter(os, null);
207 } catch (Exception e) {
209 System.out.println(e.toString());
218 public void writeByteAsInt(int b) {
224 } catch (IOException e) {
231 public void write(byte[] buf, int i, int len) {
235 os.write(buf, i, len);
236 } catch (IOException e) {
242 public void writeShort(short i) {
244 writeByteAsInt(i >> 8);
248 writeByteAsInt(i >> 8);
253 public void writeLong(long b) {
255 writeInt((int) ((b >> 32) & 0xFFFFFFFFl));
256 writeInt((int) (b & 0xFFFFFFFFl));
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);
269 public void write(int b) {
270 // required by standard ZipOutputStream -- do not use, as it will break JavaScript methods
275 } catch (IOException e) {
280 public void write(byte[] b) {
281 // not used in JavaScript due to overloading problem there
282 write(b, 0, b.length);
285 public void cancel() {
291 @SuppressWarnings({ "unused" })
292 public String closeChannel() {
295 // can't cancel file writers
300 } else if (os != null) {
304 if (os0 != null && isCanceled) {
308 } catch (Exception e) {
309 // ignore closing issues
315 if (fileName == null) {
317 String s = getBase64();
325 return closeChannel();
327 return (sb == null ? null : sb.toString());
330 J2SObjectInterface J2S = null;
331 Object _function = null;
335 * J2S = self.J2S || self.Jmol; _function = (typeof this.fileName == "function" ?
336 * this.fileName : null);
341 String ret = postByteArray(); // unsigned applet could do this
342 if (ret.startsWith("java.net"))
347 if (J2S != null && !isTemp) {
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);
355 J2S.applyFunc(_function, data);
360 public boolean isBase64() {
364 public String getBase64() {
365 return Base64.getBase64(toByteArray()).toString();
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())));
375 public void close() {
380 public String toString() {
384 } catch (IOException e) {
388 return closeChannel();
389 return byteCount + " bytes";
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.
398 private String postByteArray() {
399 return bytePoster == null ? null : bytePoster.postByteArray(fileName, toByteArray());
402 public final static String[] urlPrefixes = { "http:", "https:", "sftp:", "ftp:",
404 // note that SFTP is not supported
405 public final static int URL_LOCAL = 4;
407 public static boolean isRemote(String fileName) {
408 if (fileName == null)
410 int itype = urlTypeIndex(fileName);
411 return (itype >= 0 && itype != URL_LOCAL);
414 public static boolean isLocal(String fileName) {
415 if (fileName == null)
417 int itype = urlTypeIndex(fileName);
418 return (itype < 0 || itype == URL_LOCAL);
421 public static int urlTypeIndex(String name) {
423 return -2; // local unsigned applet
424 for (int i = 0; i < urlPrefixes.length; ++i) {
425 if (name.startsWith(urlPrefixes[i])) {
433 public void writeInt(int i) {
435 writeByteAsInt(i >> 24);
436 writeByteAsInt(i >> 16);
437 writeByteAsInt(i >> 8);
441 writeByteAsInt(i >> 8);
442 writeByteAsInt(i >> 16);
443 writeByteAsInt(i >> 24);
447 public void writeFloat(float x) {
448 writeInt(x == 0 ? 0 : Float.floatToIntBits(x));