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