package com.sas.unravl.generators;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BinaryNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sas.unravl.UnRAVL;
import com.sas.unravl.UnRAVLException;
import com.sas.unravl.util.Json;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
/**
* This class produces a binary byte stream from a JSON 'binary' specification.
*
* <pre>
* { "binary" : array-of-bytes } // the binary value is the result
* { "binary" : "@fileOrUrl" } // read binary data from a File or URL
* { "binary" : array-of-binary } // combine binary streams, each of which may be a array-of-bytes or a @file-or-url
* </pre>
* <p>
* TODO: allow variable references <code>"varName"</code>; the value must be an
* array of byte values.
* </p>
*
* @author David.Biesack@sas.com
*/
public class Binary {
private static final int BUFSIZE = 256;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
private final UnRAVL script;
/**
* Create an instance from a JSON object; the value associated with the
* field name defines how to create the binary data. For example, for the
* ObjectNode <code>{ "binary" : [ 0, 2, 1, 3 ]}</code> the constructor new
* Binary(object, "binary")
*
* @param script
* the current UnRAVL script
* @param node
* the JSON node for this scriptlet
* @param fieldName
* the field name (normally "binary")
*
* @throws IOException
* if an I/O exception occurs
* @throws UnRAVLException
* if an other exception occurs, including invalid JSON
* specification.
*/
public Binary(UnRAVL script, ObjectNode node, String fieldName)
throws IOException, UnRAVLException {
this(script, node.get(fieldName));
}
/**
* Construct a Binary instance from the binary spec value.
*
* @param script
* the current UnRAVL script
* @param binarySpec
* either a TextNode with the value "@file-or-url", or a
* ArrayNode that contains integer byte values (0 to 255) or
* "@file-or-url" strings, or nested arrays.
*
* @throws IOException
* an I/O error occurred
* @throws UnRAVLException
* Some other exception occurred, including invalid JSON
* specification
*/
public Binary(UnRAVL script, JsonNode binarySpec) throws IOException,
UnRAVLException {
this.script = script;
build(binarySpec);
}
public void build(JsonNode node) throws IOException, UnRAVLException {
if (node == null) {
} else if (node.isTextual()) {
build(node.textValue());
} else if (node.isArray()) {
for (JsonNode each : Json.array(node)) {
build(each);
}
} else if (node.isInt()) {
int b = node.asInt();
if (b < 0 | b > 255)
throw new UnRAVLException("Byte value " + b
+ " not in [0..255] in binary element");
bytes.write(b);
} else if (node.isBinary()) {
BinaryNode b = (BinaryNode) node;
bytes.write(b.binaryValue());
} else {
throw new UnRAVLException("Invalid element " + node
+ " in binary element.");
}
}
private void build(String node) throws IOException, UnRAVLException {
if (node.startsWith(UnRAVL.REDIRECT_PREFIX)) {
String path = node.substring(UnRAVL.REDIRECT_PREFIX.length());
path = script.expand(path);
buildFromStream(path);
} else {
throw new UnRAVLException("Unrecognized element " + node
+ " in 'binary' input.");
}
}
private void buildFromStream(String fileOrURL) throws IOException {
InputStream is = null;
try {
URL url = new URL(fileOrURL);
is = url.openStream();
} catch (MalformedURLException e) {
File f = new File(fileOrURL);
if (f.exists()) {
is = new FileInputStream(f);
} else {
is = getClass().getResourceAsStream(fileOrURL);
}
}
if (is == null) {
throw new IOException("No such file or URL " + fileOrURL);
}
copy(is, bytes);
}
/**
* Copy bytes from an input stream to an output stream.
*
* @param in
* the input stream. This is closed when done.
* @param out
* the output stream. This is <strong>not</strong> closed.
* @throws IOException
* if there is an error reading from in or writing to out
*/
public static void copy(InputStream in, OutputStream out)
throws IOException {
byte buffer[] = new byte[BUFSIZE];
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
for (int n = bis.read(buffer, 0, BUFSIZE); n > 0; n = bis.read(buffer,
0, BUFSIZE))
bos.write(buffer, 0, n);
bos.flush();
bis.close();
}
public InputStream stream() {
return new ByteArrayInputStream(bytes.toByteArray());
}
public int size() {
return bytes.size();
}
@Override
public boolean equals(Object other) {
if (other == null)
return false;
else if (other instanceof Binary) {
return bytes.equals(((Binary) other).bytes);
} else
return false;
}
public byte[] bytes() {
return bytes.toByteArray();
}
}