--- /dev/null
+package javajs.util;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javajs.api.BytePoster;
+import javajs.api.GenericOutputChannel;
+import javajs.api.js.J2SObjectInterface;
+
+/**
+ *
+ * A generic output method. JmolOutputChannel can be used to:
+ *
+ * add characters to a StringBuffer
+ * using fileName==null, append() and toString()
+ *
+ * add bytes utilizing ByteArrayOutputStream
+ * using writeBytes(), writeByteAsInt(), append()*, and bytesAsArray()
+ * *append() can be used as long as os==ByteArrayOutputStream
+ * or it is not used before one of the writeByte methods.
+ *
+ * output characters to a FileOutputStream
+ * using os==FileOutputStream, asWriter==true, append(), and closeChannel()
+ *
+ * output bytes to a FileOutputStream
+ * using os==FileOutputStream, writeBytes(), writeByteAsInt(), append(), and closeChannel()
+ *
+ * post characters or bytes to a remote server
+ * using fileName=="http://..." or "https://...",
+ * writeBytes(), writeByteAsInt(), append(), and closeChannel()
+ *
+ * send characters or bytes to a JavaScript function
+ * when JavaScript and (typeof fileName == "function")
+ *
+ * if fileName equals ";base64,", then the data are base64-encoded
+ * prior to writing, and closeChannel() returns the data.
+ *
+ * @author hansonr Bob Hanson hansonr@stolaf.edu 9/2013
+ *
+ *
+ */
+
+public class OC extends OutputStream implements GenericOutputChannel {
+
+ private BytePoster bytePoster; // only necessary for writing to http:// or https://
+ private String fileName;
+ private BufferedWriter bw;
+ private boolean isLocalFile;
+ private int byteCount;
+ private boolean isCanceled;
+ private boolean closed;
+ private OutputStream os;
+ private SB sb;
+ private String type;
+ private boolean isBase64;
+ private OutputStream os0;
+ private byte[] bytes; // preset bytes; output only
+
+ public boolean bigEndian = true;
+
+ private boolean isTemp;
+
+ /**
+ * Setting isTemp=true informs OC that this is a temporary file
+ * and not to send it to the user as a "download". Instead, the calling
+ * class can use .toByteArray() to retrieve the byte[] result.
+ *
+ * @param tf
+ */
+ public void setTemp(boolean tf) {
+ isTemp = tf;
+ }
+
+
+
+ @Override
+ public boolean isBigEndian() {
+ return bigEndian;
+ }
+
+ public void setBigEndian(boolean TF) {
+ bigEndian = TF;
+ }
+
+ /**
+ * Set up an output channel. String or byte data can be added without problem.
+ *
+ * @param bytePoster a byte poster can take the output byte[] when closing and
+ * do something with them
+ * @param fileName TODO: It is possible that JavaScript will call this with a
+ * function name for fileName
+ * @param asWriter string-based
+ * @param os the desired target OutputStream - not the calling stream!
+ * @return
+ */
+ public OC setParams(BytePoster bytePoster, String fileName,
+ boolean asWriter, OutputStream os) {
+ this.bytePoster = bytePoster;
+ isBase64 = ";base64,".equals(fileName);
+ if (isBase64) {
+ fileName = null;
+ os0 = os;
+ os = null;
+ }
+ this.fileName = fileName;
+ this.os = os;
+ isLocalFile = (fileName != null && !isRemote(fileName));
+ if (asWriter && !isBase64 && os != null)
+ bw = Rdr.getBufferedWriter(os, null);
+ return this;
+ }
+
+ public OC setBytes(byte[] b) {
+ bytes = b;
+ return this;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public String getName() {
+ return (fileName == null ? null : fileName.substring(fileName.lastIndexOf("/") + 1));
+ }
+
+ public int getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ *
+ * @param type user-identified type (PNG, JPG, etc)
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * will go to string buffer if bw == null and os == null
+ *
+ * @param s
+ * @return this, for chaining like a standard StringBuffer
+ *
+ */
+ public OC append(String s) {
+ try {
+ if (bw != null) {
+ bw.write(s);
+ } else if (os == null) {
+ if (sb == null)
+ sb = new SB();
+ sb.append(s);
+ } else {
+ byte[] b = s.getBytes();
+ os.write(b, 0, b.length);
+ byteCount += b.length;
+ return this;
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ byteCount += s.length(); // not necessarily exactly correct if unicode
+ return this;
+ }
+
+ @Override
+ public void reset() {
+ sb = null;
+ initOS();
+ }
+
+
+ private void initOS() {
+ if (sb != null) {
+ String s = sb.toString();
+ reset();
+ append(s);
+ return;
+ }
+ try {
+ /**
+ * @j2sNative
+ *
+ * this.os = null;
+ */
+ {
+ if (os instanceof FileOutputStream) {
+ os.close();
+ os = new FileOutputStream(fileName);
+ } else {
+ os = null;
+ }
+ }
+ if (os == null)
+ os = new ByteArrayOutputStream();
+ if (bw != null) {
+ bw.close();
+ bw = Rdr.getBufferedWriter(os, null);
+ }
+ } catch (Exception e) {
+ // not perfect here.
+ System.out.println(e.toString());
+ }
+ byteCount = 0;
+ }
+
+ /**
+ * @param b
+ */
+ @Override
+ public void writeByteAsInt(int b) {
+ if (os == null)
+ initOS();
+ {
+ try {
+ os.write(b);
+ } catch (IOException e) {
+ }
+ }
+ byteCount++;
+ }
+
+ @Override
+ public void write(byte[] buf, int i, int len) {
+ if (os == null)
+ initOS();
+ try {
+ os.write(buf, i, len);
+ } catch (IOException e) {
+ }
+ byteCount += len;
+ }
+
+ @Override
+ public void writeShort(short i) {
+ if (isBigEndian()) {
+ writeByteAsInt(i >> 8);
+ writeByteAsInt(i);
+ } else {
+ writeByteAsInt(i);
+ writeByteAsInt(i >> 8);
+ }
+ }
+
+ @Override
+ public void writeLong(long b) {
+ if (isBigEndian()) {
+ writeInt((int) ((b >> 32) & 0xFFFFFFFFl));
+ writeInt((int) (b & 0xFFFFFFFFl));
+ } else {
+ writeByteAsInt((int) (b >> 56));
+ writeByteAsInt((int) (b >> 48));
+ writeByteAsInt((int) (b >> 40));
+ writeByteAsInt((int) (b >> 32));
+ writeByteAsInt((int) (b >> 24));
+ writeByteAsInt((int) (b >> 16));
+ writeByteAsInt((int) (b >> 8));
+ writeByteAsInt((int) b);
+ }
+ }
+
+ public void write(int b) {
+ // required by standard ZipOutputStream -- do not use, as it will break JavaScript methods
+ if (os == null)
+ initOS();
+ try {
+ os.write(b);
+ } catch (IOException e) {
+ }
+ byteCount++;
+ }
+
+ public void write(byte[] b) {
+ // not used in JavaScript due to overloading problem there
+ write(b, 0, b.length);
+ }
+
+ public void cancel() {
+ isCanceled = true;
+ closeChannel();
+ }
+
+ @Override
+ @SuppressWarnings({ "unused" })
+ public String closeChannel() {
+ if (closed)
+ return null;
+ // can't cancel file writers
+ try {
+ if (bw != null) {
+ bw.flush();
+ bw.close();
+ } else if (os != null) {
+ os.flush();
+ os.close();
+ }
+ if (os0 != null && isCanceled) {
+ os0.flush();
+ os0.close();
+ }
+ } catch (Exception e) {
+ // ignore closing issues
+ }
+ if (isCanceled) {
+ closed = true;
+ return null;
+ }
+ if (fileName == null) {
+ if (isBase64) {
+ String s = getBase64();
+ if (os0 != null) {
+ os = os0;
+ append(s);
+ }
+ sb = new SB();
+ sb.append(s);
+ isBase64 = false;
+ return closeChannel();
+ }
+ return (sb == null ? null : sb.toString());
+ }
+ closed = true;
+ J2SObjectInterface J2S = null;
+ Object _function = null;
+ /**
+ * @j2sNative
+ *
+ * J2S = self.J2S || self.Jmol; _function = (typeof this.fileName == "function" ?
+ * this.fileName : null);
+ *
+ */
+ {
+ if (!isLocalFile) {
+ String ret = postByteArray(); // unsigned applet could do this
+ if (ret.startsWith("java.net"))
+ byteCount = -1;
+ return ret;
+ }
+ }
+ if (J2S != null && !isTemp) {
+
+ // action here generally will be for the browser to display a download message
+ // temp files will not be sent this way.
+ Object data = (sb == null ? toByteArray() : sb.toString());
+ if (_function == null)
+ J2S.doAjax(fileName, null, data, sb == null);
+ else
+ J2S.applyFunc(_function, data);
+ }
+ return null;
+ }
+
+ public boolean isBase64() {
+ return isBase64;
+ }
+
+ public String getBase64() {
+ return Base64.getBase64(toByteArray()).toString();
+ }
+
+ public byte[] toByteArray() {
+ return (bytes != null ? bytes : (bytes = os instanceof ByteArrayOutputStream ? ((ByteArrayOutputStream)os).toByteArray() :
+ sb == null ? null : sb.toBytes(0, sb.length())));
+ }
+
+ @Override
+ @Deprecated
+ public void close() {
+ closeChannel();
+ }
+
+ @Override
+ public String toString() {
+ if (bw != null)
+ try {
+ bw.flush();
+ } catch (IOException e) {
+ // TODO
+ }
+ if (sb != null)
+ return closeChannel();
+ return byteCount + " bytes";
+ }
+
+ /**
+ * We have constructed some sort of byte[] that needs to be posted to a remote site.
+ * We don't do that posting here -- we let the bytePoster do it.
+ *
+ * @return
+ */
+ private String postByteArray() {
+ return bytePoster == null ? null : bytePoster.postByteArray(fileName, toByteArray());
+ }
+
+ public final static String[] urlPrefixes = { "http:", "https:", "sftp:", "ftp:",
+ "file:" };
+ // note that SFTP is not supported
+ public final static int URL_LOCAL = 4;
+
+ public static boolean isRemote(String fileName) {
+ if (fileName == null)
+ return false;
+ int itype = urlTypeIndex(fileName);
+ return (itype >= 0 && itype != URL_LOCAL);
+ }
+
+ public static boolean isLocal(String fileName) {
+ if (fileName == null)
+ return false;
+ int itype = urlTypeIndex(fileName);
+ return (itype < 0 || itype == URL_LOCAL);
+ }
+
+ public static int urlTypeIndex(String name) {
+ if (name == null)
+ return -2; // local unsigned applet
+ for (int i = 0; i < urlPrefixes.length; ++i) {
+ if (name.startsWith(urlPrefixes[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public void writeInt(int i) {
+ if (bigEndian) {
+ writeByteAsInt(i >> 24);
+ writeByteAsInt(i >> 16);
+ writeByteAsInt(i >> 8);
+ writeByteAsInt(i);
+ } else {
+ writeByteAsInt(i);
+ writeByteAsInt(i >> 8);
+ writeByteAsInt(i >> 16);
+ writeByteAsInt(i >> 24);
+ }
+ }
+
+ public void writeFloat(float x) {
+ writeInt(x == 0 ? 0 : Float.floatToIntBits(x));
+ }
+
+}