package org.codehaus.jackson.node; import java.io.IOException; import java.util.Arrays; import org.codehaus.jackson.*; import org.codehaus.jackson.map.SerializerProvider; /** * Value node that contains Base64 encoded binary value, which will be * output and stored as Json String value. */ public final class BinaryNode extends ValueNode { final static BinaryNode EMPTY_BINARY_NODE = new BinaryNode(new byte[0]); final byte[] _data; public BinaryNode(byte[] data) { _data = data; } public BinaryNode(byte[] data, int offset, int length) { if (offset == 0 && length == data.length) { _data = data; } else { _data = new byte[length]; System.arraycopy(data, offset, _data, 0, length); } } public static BinaryNode valueOf(byte[] data) { if (data == null) { return null; } if (data.length == 0) { return EMPTY_BINARY_NODE; } return new BinaryNode(data); } public static BinaryNode valueOf(byte[] data, int offset, int length) { if (data == null) { return null; } if (length == 0) { return EMPTY_BINARY_NODE; } return new BinaryNode(data, offset, length); } @Override public JsonToken asToken() { /* No distinct type; could use one for textual values, * but given that it's not in text form at this point, * embedded-object is closest */ return JsonToken.VALUE_EMBEDDED_OBJECT; } @Override public boolean isBinary() { return true; } /** *<p> * Note: caller is not to modify returned array in any way, since * it is not a copy but reference to the underlying byte array. */ @Override public byte[] getBinaryValue() { return _data; } /** * Hmmh. This is not quite as efficient as using {@link #serialize}, * but will work correctly. */ public String getValueAsText() { return _asBase64(false, _data); } @Override public final void serialize(JsonGenerator jg, SerializerProvider provider) throws IOException, JsonProcessingException { jg.writeBinary(_data); } @Override public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; if (o.getClass() != getClass()) { // final class, can do this return false; } return Arrays.equals(((BinaryNode) o)._data, _data); } @Override public int hashCode() { return (_data == null) ? -1 : _data.length; } /** * Different from other values, since contents need to be surrounded * by (double) quotes. */ @Override public String toString() { return _asBase64(true, _data); } /* ///////////////////////////////////////////////////////////////// // Internal methods ///////////////////////////////////////////////////////////////// */ protected static String _asBase64(boolean addQuotes, byte[] input) { int inputEnd = input.length; StringBuilder sb = new StringBuilder(_outputLength(inputEnd)); if (addQuotes) { sb.append('"'); } // should there be a way to customize this? Base64Variant b64variant = Base64Variants.getDefaultVariant(); int chunksBeforeLF = b64variant.getMaxLineLength() >> 2; // Ok, first we loop through all full triplets of data: int inputPtr = 0; int safeInputEnd = inputEnd-3; // to get only full triplets while (inputPtr <= safeInputEnd) { // First, mash 3 bytes into lsb of 32-bit int int b24 = ((int) input[inputPtr++]) << 8; b24 |= ((int) input[inputPtr++]) & 0xFF; b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF); b64variant.encodeBase64Chunk(sb, b24); if (--chunksBeforeLF <= 0) { // note: must quote in JSON value, so not really useful... sb.append('\\'); sb.append('n'); chunksBeforeLF = b64variant.getMaxLineLength() >> 2; } } // And then we may have 1 or 2 leftover bytes to encode int inputLeft = inputEnd - inputPtr; // 0, 1 or 2 if (inputLeft > 0) { // yes, but do we have room for output? int b24 = ((int) input[inputPtr++]) << 16; if (inputLeft == 2) { b24 |= (((int) input[inputPtr++]) & 0xFF) << 8; } b64variant.encodeBase64Partial(sb, b24, inputLeft); } if (addQuotes) { sb.append('"'); } return sb.toString(); } private static int _outputLength(int inputLen) { // let's approximate... 33% overhead, ~= 3/8 (0.375) return inputLen + (inputLen >> 2) + (inputLen >> 3); } }