/* * Copyright © 2008-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.0 * @author Rebecca G. Bettencourt, Kreative Software */ package com.kreative.cff; import java.io.Serializable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.EOFException; public class ChunkFileSpec implements Serializable { private static final long serialVersionUID = 1L; // T4.S4.d.e public static final ChunkFileSpec CFSPEC_IFF = new ChunkFileSpec( new ChunkSpec(), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM), FieldSpec.DATA }, true) ); // T4.S4l.d.e public static final ChunkFileSpec CFSPEC_RIFF = new ChunkFileSpec( new ChunkSpec(), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM, true), FieldSpec.DATA }, true) ); // T4.S4.d public static final ChunkFileSpec CFSPEC_MIDI = new ChunkFileSpec( new ChunkSpec(), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM), FieldSpec.DATA }) ); // M8:S4.T4.d.H4 public static final ChunkFileSpec CFSPEC_PNG = new ChunkFileSpec( new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.INTEGER_TYPE, FieldSize.LONG) }), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM), new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), FieldSpec.DATA, new FieldSpec(FieldType.CHECKSUM, FieldSize.MEDIUM) }) ); // T4.Z4:T4.Z4.d public static final ChunkFileSpec CFSPEC_ICNS = new ChunkFileSpec( new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.SIZE_WITH_HEADER, FieldSize.MEDIUM) }), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.SIZE_WITH_HEADER, FieldSize.MEDIUM), FieldSpec.DATA }) ); // Z4.T4.N4.F4.d public static final ChunkFileSpec CFSPEC_HYPERCARD = new ChunkFileSpec( new ChunkSpec(), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.SIZE_WITH_HEADER, FieldSize.MEDIUM), new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM), new FieldSpec(FieldType.ID_NUMBER, FieldSize.MEDIUM), new FieldSpec(FieldType.FILLER, FieldSize.MEDIUM), FieldSpec.DATA }) ); // T4:T8.N2.F2.S4.d public static final ChunkFileSpec CFSPEC_DFF1BE = new ChunkFileSpec( new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM) }), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.LONG), new FieldSpec(FieldType.ID_NUMBER, FieldSize.SHORT), new FieldSpec(FieldType.FILLER, FieldSize.SHORT), new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM), FieldSpec.DATA }) ); // T4l:T8l.N2l.F2l.S4l.d public static final ChunkFileSpec CFSPEC_DFF1LE = new ChunkFileSpec( new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.MEDIUM, true) }), new ChunkSpec(new FieldSpec[] { new FieldSpec(FieldType.CHARACTER_TYPE, FieldSize.LONG, true), new FieldSpec(FieldType.ID_NUMBER, FieldSize.SHORT, true), new FieldSpec(FieldType.FILLER, FieldSize.SHORT, true), new FieldSpec(FieldType.SIZE_WITHOUT_HEADER, FieldSize.MEDIUM, true), FieldSpec.DATA }) ); private ChunkSpec fileHeaderSpec; private ChunkSpec chunkHeaderSpec; public ChunkFileSpec(ChunkSpec fileHS, ChunkSpec chunkHS) { fileHeaderSpec = fileHS; chunkHeaderSpec = chunkHS; } public ChunkFileSpec(String spec) { String[] things = spec.trim().split("[:;=]+"); switch (things.length) { case 0: fileHeaderSpec = new ChunkSpec(); chunkHeaderSpec = new ChunkSpec(); break; case 1: fileHeaderSpec = new ChunkSpec(); chunkHeaderSpec = new ChunkSpec(things[0]); break; default: fileHeaderSpec = new ChunkSpec(things[0]); chunkHeaderSpec = new ChunkSpec(things[1]); break; } } public ChunkFileSpec(byte[] spec, int index) { fileHeaderSpec = new ChunkSpec(spec, index); chunkHeaderSpec = new ChunkSpec(spec, index+1+fileHeaderSpec.size()); } public ChunkSpec fileHeaderSpec() { return fileHeaderSpec; } public ChunkSpec chunkHeaderSpec() { return chunkHeaderSpec; } public String stringRepresentation() { String a = fileHeaderSpec.stringRepresentation(); String b = chunkHeaderSpec.stringRepresentation(); if (a == null || a.length() == 0) return b; else return a + ":" + b; } public byte[] bitPatternRepresentation() { byte[] a = fileHeaderSpec.bitPatternRepresentation(); byte[] b = chunkHeaderSpec.bitPatternRepresentation(); byte[] c = new byte[a.length + b.length]; int ci = 0; for (int ai = 0; ai < a.length; ai++, ci++) c[ci] = a[ai]; for (int bi = 0; bi < b.length; bi++, ci++) c[ci] = b[bi]; return c; } public ChunkFile readChunkHeaders(DataInput in) throws IOException { Header fh = fileHeaderSpec.readHeader(in); ChunkFile cf = new ChunkFile(fh); if (fh.containsKey(FieldType.CHUNK_COUNT)) { long i = 0; long c = fh.get(FieldType.CHUNK_COUNT).longValue(); while (i < c) { Header h = chunkHeaderSpec.readHeader(in); i++; cf.add(new Chunk(h, new byte[0])); } } else if (fh.containsKey(FieldType.SIZE_WITHOUT_HEADER)) { long i = 0; long c = fh.get(FieldType.SIZE_WITHOUT_HEADER).longValue(); while (i < c) { Header h = chunkHeaderSpec.readHeader(in); long dl = ( h.containsKey(FieldType.SIZE_WITH_HEADER) ? h.get(FieldType.SIZE_WITH_HEADER).longValue() : h.containsKey(FieldType.SIZE_WITHOUT_HEADER) ? (chunkHeaderSpec.byteCount() + h.get(FieldType.SIZE_WITHOUT_HEADER).longValue()) : 0 ); if (chunkHeaderSpec.evenPadded() && (dl % 2) == 1) dl++; i += dl; cf.add(new Chunk(h, new byte[0])); } } else if (fh.containsKey(FieldType.SIZE_WITH_HEADER)) { long i = fileHeaderSpec.byteCount(); long c = fh.get(FieldType.SIZE_WITH_HEADER).longValue(); while (i < c) { Header h = chunkHeaderSpec.readHeader(in); long dl = ( h.containsKey(FieldType.SIZE_WITH_HEADER) ? h.get(FieldType.SIZE_WITH_HEADER).longValue() : h.containsKey(FieldType.SIZE_WITHOUT_HEADER) ? (chunkHeaderSpec.byteCount() + h.get(FieldType.SIZE_WITHOUT_HEADER).longValue()) : 0 ); if (chunkHeaderSpec.evenPadded() && (dl % 2) == 1) dl++; i += dl; cf.add(new Chunk(h, new byte[0])); } } else { while (true) { try { Header h = chunkHeaderSpec.readHeader(in); cf.add(new Chunk(h, new byte[0])); } catch (EOFException eof) { break; } } } return cf; } public ChunkFile readChunkFile(DataInput in) throws IOException { Header fh = fileHeaderSpec.readHeader(in); ChunkFile cf = new ChunkFile(fh); if (fh.containsKey(FieldType.CHUNK_COUNT)) { long i = 0; long c = fh.get(FieldType.CHUNK_COUNT).longValue(); while (i < c) { Chunk ch = chunkHeaderSpec.readChunk(in); i++; cf.add(ch); } } else if (fh.containsKey(FieldType.SIZE_WITHOUT_HEADER)) { long i = 0; long c = fh.get(FieldType.SIZE_WITHOUT_HEADER).longValue(); while (i < c) { Chunk ch = chunkHeaderSpec.readChunk(in); i += chunkHeaderSpec.byteCount() + ch.getData().length; if (chunkHeaderSpec.evenPadded() && ((ch.getData().length % 2) == 1)) i++; cf.add(ch); } } else if (fh.containsKey(FieldType.SIZE_WITH_HEADER)) { long i = fileHeaderSpec.byteCount(); long c = fh.get(FieldType.SIZE_WITH_HEADER).longValue(); while (i < c) { Chunk ch = chunkHeaderSpec.readChunk(in); i += chunkHeaderSpec.byteCount() + ch.getData().length; if (chunkHeaderSpec.evenPadded() && ((ch.getData().length % 2) == 1)) i++; cf.add(ch); } } else { while (true) { try { Chunk ch = chunkHeaderSpec.readChunk(in); cf.add(ch); } catch (EOFException eof) { break; } } } return cf; } public void writeChunkFile(DataOutput out, ChunkFile cf) throws IOException { Header fh = cf.getHeader(); if (fh.containsKey(FieldType.CHUNK_COUNT)) { fh.put(FieldType.CHUNK_COUNT, cf.size()); } if (fh.containsKey(FieldType.SIZE_WITHOUT_HEADER)) { long l = chunkHeaderSpec.byteCount() * cf.size(); for (Chunk ch : cf) { l += ch.getData().length; if (chunkHeaderSpec.evenPadded() && (ch.getData().length % 2) == 1) l++; } fh.put(FieldType.SIZE_WITHOUT_HEADER, l); } if (fh.containsKey(FieldType.SIZE_WITH_HEADER)) { long l = fileHeaderSpec.byteCount() + chunkHeaderSpec.byteCount() * cf.size(); for (Chunk ch : cf) { l += ch.getData().length; if (chunkHeaderSpec.evenPadded() && (ch.getData().length % 2) == 1) l++; } fh.put(FieldType.SIZE_WITH_HEADER, l); } fileHeaderSpec.writeHeader(out, fh); for (Chunk ch : cf) chunkHeaderSpec.writeChunk(out, ch); } public boolean equals(Object o) { return ( o instanceof ChunkFileSpec && ((ChunkFileSpec)o).fileHeaderSpec.equals(fileHeaderSpec) && ((ChunkFileSpec)o).chunkHeaderSpec.equals(chunkHeaderSpec) ); } public int hashCode() { return fileHeaderSpec.hashCode() ^ chunkHeaderSpec.hashCode(); } }