/* * 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 DataReader { private List<DataField> format; public DataReader(List<DataField> format) { this.format = format; } public List<Object> unpack(byte[] b) throws IOException { return unpack(new ByteArrayInputStream(b), b.length); } public List<Object> unpack(File f) throws IOException { return unpack(new FileInputStream(f), f.length()); } public List<Object> unpack(InputStream in, long length) throws IOException { return unpack(new BitInputStream(in), length); } public List<Object> unpack(BitInputStream in, long length) throws IOException { return unpack(format, new MapStack<String,Object>(), in, length, false).listed; } public Map<String,Object> unpackNamed(byte[] b) throws IOException { return unpackNamed(new ByteArrayInputStream(b), b.length); } public Map<String,Object> unpackNamed(File f) throws IOException { return unpackNamed(new FileInputStream(f), f.length()); } public Map<String,Object> unpackNamed(InputStream in, long length) throws IOException { return unpackNamed(new BitInputStream(in), length); } public Map<String,Object> unpackNamed(BitInputStream in, long length) throws IOException { return unpack(format, new MapStack<String,Object>(), in, length, true).named; } private static class UnpackResult { public List<Object> listed = new ArrayList<Object>(); public Map<String,Object> named = new HashMap<String,Object>(); } private static UnpackResult unpack(List<DataField> format, MapStack<String,Object> map, BitInputStream in, long length, boolean named) throws IOException { UnpackResult res = new UnpackResult(); map.push(res.named); for (DataField df : format) { if (df.type().returns()) { Object o = unpackFieldWithCount(df, map, in, length, named); res.listed.add(o); if (df.name() != null) { res.named.put(df.name(), o); } } else { unpackFieldWithCount(df, map, in, length, named); } } map.pop(); return res; } private static Object unpackFieldWithCount(DataField df, MapStack<String,Object> map, BitInputStream in, long length, boolean named) throws IOException { if (df.count() == null || df.type().usesCustomCount()) { return unpackFieldWithoutCount(df, map, in, length, named); } else { int count = df.count().evaluate(map, in, length); if (df.type().returns()) { List<Object> res = new ArrayList<Object>((count < 10) ? 10 : count); while (count-->0) { res.add(unpackFieldWithoutCount(df, map, in, length, named)); } return res; } else { while (count-->0) { unpackFieldWithoutCount(df, map, in, length, named); } return null; } } } private static Object unpackFieldWithoutCount(DataField df, MapStack<String,Object> map, BitInputStream in, long length, boolean named) 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: return !in.readBits(df.size()).isEmpty(); case ENUM: BigInteger ev = df.littleEndian() ? in.readUnsignedIntegerLE(df.size()) : in.readUnsignedInteger(df.size()); Map<?,?> em = (Map<?,?>)df.elaboration(); if (em.containsKey(ev)) return em.get(ev); else return ev; case BITFIELD: BitSet bfv = df.littleEndian() ? in.readBitsLE(df.size()) : in.readBits(df.size()); List<Object> bfl = new ArrayList<Object>(); Map<?,?> bfm = (Map<?,?>)df.elaboration(); for (int i = 0; i < df.size(); i++) { if (bfv.get(i)) { BigInteger bi = BigInteger.valueOf(i); if (bfm.containsKey(bi)) { bfl.add(bfm.get(bi)); } else if (bfm.containsKey(i)) { bfl.add(bfm.get(i)); } } } return bfl; case BINT: if (df.littleEndian()) { return in.readUnsignedIntegerLE(df.size()).toString(2); } else { return in.readUnsignedInteger(df.size()).toString(2); } case OINT: if (df.littleEndian()) { return in.readUnsignedIntegerLE(df.size()).toString(8); } else { return in.readUnsignedInteger(df.size()).toString(8); } case HINT: if (df.littleEndian()) { return in.readUnsignedIntegerLE(df.size()).toString(16).toUpperCase(); } else { return in.readUnsignedInteger(df.size()).toString(16).toUpperCase(); } case UINT: if (df.littleEndian()) { return in.readUnsignedIntegerLE(df.size()); } else { return in.readUnsignedInteger(df.size()); } case SINT: if (df.littleEndian()) { return in.readIntegerLE(df.size()); } else { return in.readInteger(df.size()); } case UFIXED: if (df.littleEndian()) { return new BigDecimal(in.readUnsignedIntegerLE(df.size()), MathContext.DECIMAL128) .divide(BigDecimal.valueOf(2).pow(df.size()/2), MathContext.DECIMAL128); } else { return new BigDecimal(in.readUnsignedInteger(df.size()), MathContext.DECIMAL128) .divide(BigDecimal.valueOf(2).pow(df.size()/2), MathContext.DECIMAL128); } case SFIXED: if (df.littleEndian()) { return new BigDecimal(in.readIntegerLE(df.size()), MathContext.DECIMAL128) .divide(BigDecimal.valueOf(2).pow(df.size()/2), MathContext.DECIMAL128); } else { return new BigDecimal(in.readInteger(df.size()), MathContext.DECIMAL128) .divide(BigDecimal.valueOf(2).pow(df.size()/2), MathContext.DECIMAL128); } case FLOAT: int[] fpfmt = (int[])df.elaboration(); if (df.littleEndian()) { return in.readFloatLE(fpfmt[0], fpfmt[1], fpfmt[2], fpfmt[3], MathContext.DECIMAL128); } else { return in.readFloat(fpfmt[0], fpfmt[1], fpfmt[2], fpfmt[3], MathContext.DECIMAL128); } case COMPLEX: int[] fpfmt1 = (int[])df.elaboration(); if (df.littleEndian()) { Number r = in.readFloatLE(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128); Number i = in.readFloatLE(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128); return new Number[]{r,i}; } else { Number r = in.readFloat(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128); Number i = in.readFloat(fpfmt1[0], fpfmt1[1], fpfmt1[2], fpfmt1[3], MathContext.DECIMAL128); return new Number[]{r,i}; } 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[] chb = new byte[chwidth]; in.readFully(chb); if (df.littleEndian()) { for (int i = 0, j = chb.length-1; i < chb.length/2; i++, j--) { byte k = chb[i]; chb[i] = chb[j]; chb[j] = k; } } return new String(chb, df.elaboration().toString()); case PSTRING: int pstrwidth = (df.littleEndian() ? in.readUnsignedIntegerLE(df.size()) : in.readUnsignedInteger(df.size())).intValue(); byte[] pstrb = new byte[pstrwidth]; in.readFully(pstrb); return new String(pstrb, df.elaboration().toString()); case CSTRING: if ((df.size() & 7) != 0) throw new IOException("C-string values must be of a byte-multiple width"); int cstrwidth = (df.size() >> 3); byte[] cstrb = new byte[cstrwidth]; in.readFully(cstrb); ByteArrayOutputStream cstrout = new ByteArrayOutputStream(); while (true) { boolean end = true; for (byte b : cstrb) { if (b != 0) end = false; } if (end) break; cstrout.write(cstrb[0]); for (int i = 1; i < cstrb.length; i++) { cstrb[i-1] = cstrb[i]; } cstrb[cstrb.length-1] = in.readByte(); } return new String(cstrout.toByteArray(), df.elaboration().toString()); case DATE: DateFormat datefmt = (DateFormat)df.elaboration(); if (df.littleEndian()) { return datefmt.longToCalendar(in.readIntegerLE(df.size()).longValue()); } else { return datefmt.longToCalendar(in.readInteger(df.size()).longValue()); } case COLOR: ColorFormat colorfmt = (ColorFormat)df.elaboration(); BigInteger colorval = df.littleEndian() ? in.readUnsignedIntegerLE(df.size()) : in.readUnsignedInteger(df.size()); Number[] colorvals = new Number[colorfmt.channelCount()]; int colorshift = 0; for (int i = colorfmt.channelCount()-1; i >= 0; i--) { colorvals[i] = colorval.shiftRight(colorshift).and(BigInteger.ONE.shiftLeft(colorfmt.channelWidth(i)).subtract(BigInteger.ONE)); colorshift += colorfmt.channelWidth(i); } return colorfmt.toRGBAFloatArray(colorfmt.toFloatArray(colorvals)); case FILLER: in.skipBits(df.size()); return null; case MAGIC: BigInteger magicMask = BigInteger.ONE.shiftLeft(df.size()).subtract(BigInteger.ONE); BigInteger magicTarget = ((BigInteger)df.elaboration()).and(magicMask); BigInteger magicBullet = df.littleEndian() ? in.readUnsignedIntegerLE(df.size()) : in.readUnsignedInteger(df.size()); if (magicBullet.compareTo(magicTarget) != 0) { throw new IOException("Magic numbers do not match"); } return null; case ALIGN: while (!in.atBitBoundary(df.size())) { in.skipBit(); } return null; case BINARY: if (df.count() != null) { int len = df.count().evaluate(map, in, length); byte[] b = new byte[len]; in.readFully(b); return b; } return new byte[0]; case STRUCT: @SuppressWarnings("unchecked") List<DataField> format = (List<DataField>)df.elaboration(); UnpackResult ur = unpack(format, map, in, length, named); return (named ? ur.named : ur.listed); case OFFSET: if (df.count() != null) { int offset = df.count().evaluate(map, in, length); in.reset(); in.skipBytes(offset); } return null; default: throw new RuntimeException("Unknown data type: " + df.type().toString()); } } }