/* * plist - An open source library to parse and generate property lists * Copyright (C) 2012 Keith Randall * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.dd.plist; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.LinkedHashMap; import java.util.Map; /** * A BinaryPropertyListWriter is a helper class for writing out * binary property list files. It contains an output stream and * various structures for keeping track of which NSObjects have * already been serialized, and where they were put in the file. * * @author Keith Randall */ public class BinaryPropertyListWriter { public static final int VERSION_00 = 0; public static final int VERSION_10 = 10; public static final int VERSION_15 = 15; public static final int VERSION_20 = 20; /** * Finds out the minimum binary property list format version that * can be used to save the given NSObject tree. * * @param root Object root * @return Version code */ private static int getMinimumRequiredVersion(NSObject root) { int minVersion = VERSION_00; if (root == null) { minVersion = VERSION_10; } if (root instanceof NSDictionary) { NSDictionary dict = (NSDictionary) root; for (NSObject o : dict.getHashMap().values()) { int v = getMinimumRequiredVersion(o); if (v > minVersion) minVersion = v; } } else if (root instanceof NSArray) { NSArray array = (NSArray) root; for (NSObject o : array.getArray()) { int v = getMinimumRequiredVersion(o); if (v > minVersion) minVersion = v; } } else if (root instanceof NSSet) { //Sets are only allowed in property lists v1+ minVersion = VERSION_10; NSSet set = (NSSet) root; for (NSObject o : set.allObjects()) { int v = getMinimumRequiredVersion(o); if (v > minVersion) minVersion = v; } } return minVersion; } /** * Writes a binary plist file with the given object as the root. * * @param file the file to write to * @param root the source of the data to write to the file * @throws IOException When an IO error occurs while writing to the file or the object structure contains * data that cannot be saved. */ public static void write(File file, NSObject root) throws IOException { OutputStream out = new FileOutputStream(file); write(out, root); out.close(); } /** * Writes a binary plist serialization of the given object as the root. * * @param out the stream to write to * @param root the source of the data to write to the stream * @throws IOException When an IO error occurs while writing to the stream or the object structure contains * data that cannot be saved. */ public static void write(OutputStream out, NSObject root) throws IOException { int minVersion = getMinimumRequiredVersion(root); if (minVersion > VERSION_00) { String versionString = ((minVersion == VERSION_10) ? "v1.0" : ((minVersion == VERSION_15) ? "v1.5" : ((minVersion == VERSION_20) ? "v2.0" : "v0.0"))); throw new IOException("The given property list structure cannot be saved. " + "The required version of the binary format (" + versionString + ") is not yet supported."); } BinaryPropertyListWriter w = new BinaryPropertyListWriter(out, minVersion); w.write(root); } /** * Writes a binary plist serialization of the given object as the root * into a byte array. * * @param root The root object of the property list * @return The byte array containing the serialized property list * @throws IOException When an IO error occurs while writing to the stream or the object structure contains * data that cannot be saved. */ public static byte[] writeToArray(NSObject root) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); write(bout, root); return bout.toByteArray(); } private int version = VERSION_00; // raw output stream to result file private OutputStream out; // # of bytes written so far private long count; // map from object to its ID private Map<NSObject, Integer> idMap = new LinkedHashMap<NSObject, Integer>(); private int idSizeInBytes; /** * Creates a new binary property list writer * * @param outStr The output stream into which the binary property list will be written * @throws IOException When an IO error occurs while writing to the stream or the object structure contains * data that cannot be saved. */ BinaryPropertyListWriter(OutputStream outStr) throws IOException { out = new BufferedOutputStream(outStr); } BinaryPropertyListWriter(OutputStream outStr, int version) throws IOException { this.version = version; out = new BufferedOutputStream(outStr); } void write(NSObject root) throws IOException { // magic bytes write(new byte[]{'b', 'p', 'l', 'i', 's', 't'}); //version switch (version) { case VERSION_00: { write(new byte[]{'0', '0'}); break; } case VERSION_10: { write(new byte[]{'1', '0'}); break; } case VERSION_15: { write(new byte[]{'1', '5'}); break; } case VERSION_20: { write(new byte[]{'2', '0'}); break; } } // assign IDs to all the objects. root.assignIDs(this); idSizeInBytes = computeIdSizeInBytes(idMap.size()); // offsets of each object, indexed by ID long[] offsets = new long[idMap.size()]; // write each object, save offset for (Map.Entry<NSObject, Integer> entry : idMap.entrySet()) { NSObject obj = entry.getKey(); int id = entry.getValue(); offsets[id] = count; if (obj == null) { write(0x00); } else { obj.toBinary(this); } } // write offset table long offsetTableOffset = count; int offsetSizeInBytes = computeOffsetSizeInBytes(count); for (long offset : offsets) { writeBytes(offset, offsetSizeInBytes); } if (version != VERSION_15) { // write trailer // 6 null bytes write(new byte[6]); // size of an offset write(offsetSizeInBytes); // size of a ref write(idSizeInBytes); // number of objects writeLong(idMap.size()); // top object writeLong(idMap.get(root)); // offset table offset writeLong(offsetTableOffset); } out.flush(); } void assignID(NSObject obj) { if (!idMap.containsKey(obj)) { idMap.put(obj, idMap.size()); } } int getID(NSObject obj) { return idMap.get(obj); } private static int computeIdSizeInBytes(int numberOfIds) { if (numberOfIds < 256) return 1; if (numberOfIds < 65536) return 2; return 4; } private int computeOffsetSizeInBytes(long maxOffset) { if (maxOffset < 256) return 1; if (maxOffset < 65536) return 2; if (maxOffset < 4294967296L) return 4; return 8; } void writeIntHeader(int kind, int value) throws IOException { assert value >= 0; if (value < 15) { write((kind << 4) + value); } else if (value < 256) { write((kind << 4) + 15); write(0x10); writeBytes(value, 1); } else if (value < 65536) { write((kind << 4) + 15); write(0x11); writeBytes(value, 2); } else { write((kind << 4) + 15); write(0x12); writeBytes(value, 4); } } void write(int b) throws IOException { out.write(b); count++; } void write(byte[] bytes) throws IOException { out.write(bytes); count += bytes.length; } void writeBytes(long value, int bytes) throws IOException { // write low-order bytes big-endian style for (int i = bytes - 1; i >= 0; i--) { write((int) (value >> (8 * i))); } } void writeID(int id) throws IOException { writeBytes(id, idSizeInBytes); } void writeLong(long value) throws IOException { writeBytes(value, 8); } void writeDouble(double value) throws IOException { writeLong(Double.doubleToRawLongBits(value)); } }