/*
* Copyright © 2010-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since KSFL 1.2
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.binpack;
import java.io.*;
import java.math.*;
import java.util.*;
public class DataWriter {
private List<DataField> format;
public DataWriter(List<DataField> format) {
this.format = format;
}
public byte[] pack(List<?> l) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
pack(l, out);
return out.toByteArray();
}
public void pack(List<?> l, File f) throws IOException {
pack(l, new FileOutputStream(f));
}
public void pack(List<?> l, OutputStream out) throws IOException {
pack(l, new BitOutputStream(out));
}
public void pack(List<?> l, BitOutputStream out) throws IOException {
pack(format, l, new MapStack<String,Object>(), out);
}
public byte[] pack(Map<String,?> m) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
pack(m, out);
return out.toByteArray();
}
public void pack(Map<String,?> m, File f) throws IOException {
pack(m, new FileOutputStream(f));
}
public void pack(Map<String,?> m, OutputStream out) throws IOException {
pack(m, new BitOutputStream(out));
}
public void pack(Map<String,?> m, BitOutputStream out) throws IOException {
pack(format, m, new MapStack<String,Object>(), out);
}
private static void pack(List<DataField> format, List<?> l, MapStack<String,Object> map, BitOutputStream out) throws IOException {
Map<String,Object> m = new HashMap<String,Object>();
Iterator<?> li = l.iterator();
for (DataField df : format) {
if (df.type().returns()) {
Object o = li.hasNext() ? li.next() : null;
if (df.name() != null) {
m.put(df.name(), o);
}
}
}
pack(format, l, m, map, out);
}
private static void pack(List<DataField> format, Map<String,?> m, MapStack<String,Object> map, BitOutputStream out) throws IOException {
List<Object> l = new ArrayList<Object>();
for (DataField df : format) {
if (df.type().returns()) {
if (df.name() != null && m.containsKey(df.name())) {
l.add(m.get(df.name()));
} else {
l.add(null);
}
}
}
pack(format, l, m, map, out);
}
@SuppressWarnings("unchecked")
private static void pack(List<DataField> format, List<?> l, Map<String,?> m, MapStack<String,Object> map, BitOutputStream out) throws IOException {
map.push((Map<String,Object>)m);
Iterator<?> li = l.iterator();
for (DataField df : format) {
if (df.type().returns()) {
Object o = li.hasNext() ? li.next() : null;
packFieldWithCount(df, o, map, out);
} else {
packFieldWithCount(df, null, map, out);
}
}
map.pop();
}
private static void packFieldWithCount(DataField df, Object o, MapStack<String,Object> map, BitOutputStream out) throws IOException {
if (df.count() == null || df.type().usesCustomCount()) {
packFieldWithoutCount(df, o, map, out);
} else {
int count = df.count().evaluate(map, out);
if (df.type().returns()) {
Collection<?> c;
if (o instanceof Collection) c = (Collection<?>)o;
else { ArrayList<Object> cc = new ArrayList<Object>(); cc.add(o); c = cc; }
Iterator<?> ci = c.iterator();
while (count-->0) {
Object oo = ci.hasNext() ? ci.next() : null;
packFieldWithoutCount(df, oo, map, out);
}
} else {
while (count-->0) {
packFieldWithoutCount(df, null, map, out);
}
}
}
}
private static void packFieldWithoutCount(DataField df, Object o, MapStack<String,Object> map, BitOutputStream out) throws IOException {
// here we all care about is type, size, endianness, and elaboration
// (count is accounted for in the above method, and name is accounted for two methods above)
switch (df.type()) {
case BOOLEAN:
boolean bv = ((o instanceof Boolean) && ((Boolean)o).booleanValue());
if (df.littleEndian()) out.writeIntegerLE(df.size(), bv ? BigInteger.ONE : BigInteger.ZERO);
else out.writeInteger(df.size(), bv ? BigInteger.ONE : BigInteger.ZERO);
break;
case ENUM:
String esv = (o == null ? "" : o.toString());
for (Map.Entry<?,?> e : ((Map<?,?>)df.elaboration()).entrySet()) {
if (e.getValue().toString().equalsIgnoreCase(esv)) {
BigInteger ev = (BigInteger)e.getKey();
if (df.littleEndian()) out.writeIntegerLE(df.size(), ev);
else out.writeInteger(df.size(), ev);
return;
}
}
BigInteger eiv;
if (o instanceof BigInteger) eiv = (BigInteger)o;
else if (o instanceof BigDecimal) eiv = ((BigDecimal)o).toBigInteger();
else if (o instanceof Number) eiv = BigInteger.valueOf(((Number)o).longValue());
else if (o != null) eiv = new BigInteger(o.toString());
else eiv = BigInteger.ZERO;
if (df.littleEndian()) out.writeIntegerLE(df.size(), eiv);
else out.writeInteger(df.size(), eiv);
break;
case BITFIELD:
BitSet bfv = new BitSet();
Collection<?> bfl;
if (o instanceof Collection) bfl = ((Collection<?>)o);
else { List<Object> l = new ArrayList<Object>(); l.add(o); bfl = l; }
Map<?,?> bfm = (Map<?,?>)df.elaboration();
for (int i = 0; i < df.size(); i++) {
BigInteger bi = BigInteger.valueOf(i);
if (bfm.containsKey(bi) && bfl.contains(bfm.get(bi))) {
bfv.set(i);
} else if (bfm.containsKey(i) && bfl.contains(bfm.get(i))) {
bfv.set(i);
}
}
if (df.littleEndian()) out.writeBitsLE(df.size(), bfv);
else out.writeBits(df.size(), bfv);
break;
case BINT:
BigInteger biv = (o == null) ? BigInteger.ZERO : new BigInteger(o.toString(), 2);
if (df.littleEndian()) out.writeIntegerLE(df.size(), biv);
else out.writeInteger(df.size(), biv);
break;
case OINT:
BigInteger oiv = (o == null) ? BigInteger.ZERO : new BigInteger(o.toString(), 8);
if (df.littleEndian()) out.writeIntegerLE(df.size(), oiv);
else out.writeInteger(df.size(), oiv);
break;
case HINT:
BigInteger hiv = (o == null) ? BigInteger.ZERO : new BigInteger(o.toString(), 16);
if (df.littleEndian()) out.writeIntegerLE(df.size(), hiv);
else out.writeInteger(df.size(), hiv);
break;
case UINT:
case SINT:
BigInteger uiv;
if (o instanceof BigInteger) uiv = (BigInteger)o;
else if (o instanceof BigDecimal) uiv = ((BigDecimal)o).toBigInteger();
else if (o instanceof Number) uiv = BigInteger.valueOf(((Number)o).longValue());
else if (o != null) uiv = new BigInteger(o.toString());
else uiv = BigInteger.ZERO;
if (df.littleEndian()) out.writeIntegerLE(df.size(), uiv);
else out.writeInteger(df.size(), uiv);
break;
case UFIXED:
case SFIXED:
BigDecimal fxv;
if (o instanceof BigDecimal) fxv = (BigDecimal)o;
else if (o instanceof BigInteger) fxv = new BigDecimal((BigInteger)o);
else if (o instanceof Number) fxv = BigDecimal.valueOf(((Number)o).doubleValue());
else if (o != null) fxv = new BigDecimal(o.toString());
else fxv = BigDecimal.ZERO;
fxv = fxv.multiply(BigDecimal.valueOf(2).pow(df.size()/2), MathContext.DECIMAL128);
if (df.littleEndian()) out.writeIntegerLE(df.size(), fxv.toBigInteger());
else out.writeInteger(df.size(), fxv.toBigInteger());
break;
case FLOAT:
Number flv;
if (o instanceof Number) flv = (Number)o;
else if (o != null) flv = new BigDecimal(o.toString());
else flv = 0;
int[] fpfmt = (int[])df.elaboration();
if (df.littleEndian()) {
out.writeFloatLE(fpfmt[0], fpfmt[1], fpfmt[2], fpfmt[3], MathContext.DECIMAL128, flv);
} else {
out.writeFloat(fpfmt[0], fpfmt[1], fpfmt[2], fpfmt[3], MathContext.DECIMAL128, flv);
}
break;
case COMPLEX:
Number crv, civ;
if (o instanceof Number[]) {
Number[] ccv = (Number[])o;
crv = (ccv.length > 0) ? ccv[0] : 0;
civ = (ccv.length > 1) ? ccv[1] : 0;
}
else if (o != null) {
crv = new BigDecimal(o.toString());
civ = 0;
}
else {
crv = 0;
civ = 0;
}
int[] fpfmt1 = (int[])df.elaboration();
if (df.littleEndian()) {
out.writeFloatLE(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128, crv);
out.writeFloatLE(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128, civ);
} else {
out.writeFloat(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128, crv);
out.writeFloat(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128, civ);
}
break;
case CHAR:
if ((df.size() & 7) != 0) throw new IOException("Character values must be of a byte-multiple width");
int chwidth = (df.size() >> 3);
byte[] chb1 = (o == null ? new byte[0] : o.toString().getBytes(df.elaboration().toString()));
byte[] chb2 = new byte[chwidth];
for (int i = 0; i < chb1.length && i < chb2.length; i++) chb2[i] = chb1[i];
if (df.littleEndian()) {
for (int i = 0, j = chb2.length-1; i < chb2.length/2; i++, j--) {
byte k = chb2[i];
chb2[i] = chb2[j];
chb2[j] = k;
}
}
out.write(chb2);
break;
case PSTRING:
byte[] pstrb = (o == null ? new byte[0] : o.toString().getBytes(df.elaboration().toString()));
if (df.littleEndian()) out.writeIntegerLE(df.size(), BigInteger.valueOf(pstrb.length));
else out.writeInteger(df.size(), BigInteger.valueOf(pstrb.length));
out.write(pstrb);
break;
case CSTRING:
byte[] cstrb = (o == null ? new byte[0] : o.toString().getBytes(df.elaboration().toString()));
out.write(cstrb);
out.writeInteger(df.size(), BigInteger.ZERO);
break;
case DATE:
if (o instanceof Calendar) {
Calendar c = (Calendar)o;
DateFormat datefmt = (DateFormat)df.elaboration();
if (df.littleEndian()) {
out.writeIntegerLE(df.size(), BigInteger.valueOf(datefmt.calendarToLong(c)));
} else {
out.writeInteger(df.size(), BigInteger.valueOf(datefmt.calendarToLong(c)));
}
} else if (o instanceof Date) {
Calendar c = new GregorianCalendar();
c.setTime((Date)o);
DateFormat datefmt = (DateFormat)df.elaboration();
if (df.littleEndian()) {
out.writeIntegerLE(df.size(), BigInteger.valueOf(datefmt.calendarToLong(c)));
} else {
out.writeInteger(df.size(), BigInteger.valueOf(datefmt.calendarToLong(c)));
}
} else {
out.writeInteger(df.size(), BigInteger.ZERO);
}
break;
case COLOR:
float[] color = (o instanceof float[]) ? (float[])o : new float[4];
ColorFormat colorfmt = (ColorFormat)df.elaboration();
BigInteger[] colorvals = colorfmt.toBigIntArray(colorfmt.fromRGBAFloatArray(color));
BigInteger colorval = BigInteger.ZERO;
for (int i = 0; i < colorfmt.channelCount(); i++) {
colorval = colorval.shiftLeft(colorfmt.channelWidth(i)).or(colorvals[i]);
}
if (df.littleEndian()) out.writeIntegerLE(df.size(), colorval);
else out.writeInteger(df.size(), colorval);
break;
case FILLER:
out.writeBits(df.size(), new BitSet());
break;
case MAGIC:
BigInteger magic = ((BigInteger)df.elaboration());
if (df.littleEndian()) out.writeIntegerLE(df.size(), magic);
else out.writeInteger(df.size(), magic);
break;
case ALIGN:
while (!out.atBitBoundary(df.size())) {
out.writeBit(false);
}
break;
case BINARY:
if (df.count() != null) {
int len = df.count().evaluate(map, out);
byte[] b = (o instanceof byte[]) ? (byte[])o : new byte[0];
for (int i = 0; i < b.length && i < len; i++) {
out.writeByte(b[i]);
}
for (int i = b.length; i < len; i++) {
out.writeByte(0);
}
} else if (o instanceof byte[]) {
byte[] b = (byte[])o;
out.write(b);
}
break;
case STRUCT:
@SuppressWarnings("unchecked")
List<DataField> format = (List<DataField>)df.elaboration();
if (o instanceof Map) {
@SuppressWarnings("unchecked")
Map<String,?> m = (Map<String,?>)o;
pack(format, m, map, out);
} else if (o instanceof List) {
List<?> l = (List<?>)o;
pack(format, l, map, out);
} else {
pack(format, new ArrayList<Object>(), new HashMap<String,Object>(), map, out);
}
break;
case OFFSET:
if (df.count() != null) {
int offset = df.count().evaluate(map, out);
long bitoffset = (long)offset << 3L;
if (bitoffset < out.bitsWritten()) {
throw new IOException("Can't seek backward in a pack operation");
}
while (out.bitsWritten() < bitoffset) {
out.writeBit(false);
}
}
break;
default:
throw new RuntimeException("Unknown data type: " + df.type().toString());
}
}
}