aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/java/dd_plist
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
commitd08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch)
tree5d50963026239ca5aebfb47ea5b8db7e814e57c8 /third_party/java/dd_plist
Update from Google.
-- MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'third_party/java/dd_plist')
-rw-r--r--third_party/java/dd_plist/LICENSE19
-rw-r--r--third_party/java/dd_plist/README34
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/ASCIIPropertyListParser.java645
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/Base64.java2124
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListParser.java541
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListWriter.java301
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSArray.java335
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSData.java182
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSDate.java185
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSDictionary.java539
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSNumber.java407
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSObject.java431
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSSet.java354
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/NSString.java270
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/PropertyListFormatException.java40
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/PropertyListParser.java383
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/UID.java93
-rw-r--r--third_party/java/dd_plist/java/com/dd/plist/XMLPropertyListParser.java273
18 files changed, 7156 insertions, 0 deletions
diff --git a/third_party/java/dd_plist/LICENSE b/third_party/java/dd_plist/LICENSE
new file mode 100644
index 0000000000..d187b74b49
--- /dev/null
+++ b/third_party/java/dd_plist/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 Daniel Dreibrodt
+
+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.
diff --git a/third_party/java/dd_plist/README b/third_party/java/dd_plist/README
new file mode 100644
index 0000000000..72539f9c6a
--- /dev/null
+++ b/third_party/java/dd_plist/README
@@ -0,0 +1,34 @@
+URL: http://plist.googlecode.com/svn-history/r111/trunk/
+Version: r111
+License: MIT Style
+License File: LICENSE
+
+Description:
+
+dd-plist
+This library enables Java applications to work with property lists in various
+formats.
+
+You can parse existing property lists (e.g. those created by an iOS application)
+and work with the contents on any operating system.
+
+The library also enables you to create your own property lists from scratch and
+store them in various formats.
+
+The provided API mimics the Cocoa/NeXTSTEP API, granting access to the basic
+functions of classes like NSDictionary, NSData, etc.
+
+dd-plist has full support for the Android operating system. Consequently this
+library can be of great help when it comes to porting iOS apps to Android.
+
+Local Modifications:
+- LICENSE file has been created for compliance purposes. Not included in
+ original distribution.
+- Made PropertyListParser.readAll react properly to InputStreams that sometimes
+ read fewer bytes than are available.
+- Rewrote some functions in ASCIIPropertyListParser.java to support characters
+ outside of the 7-bit ASCII range.
+- Support surrogate pairs from \u escaped chars in strings in
+ ASCIIPropertyListParser.java.
+- Allow \ escaping of characters that need not be escaped in
+ ASCIIPropertyListParser.java.
diff --git a/third_party/java/dd_plist/java/com/dd/plist/ASCIIPropertyListParser.java b/third_party/java/dd_plist/java/com/dd/plist/ASCIIPropertyListParser.java
new file mode 100644
index 0000000000..533fb391ad
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/ASCIIPropertyListParser.java
@@ -0,0 +1,645 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+ * 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.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.text.ParseException;
+import java.text.StringCharacterIterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format.
+ * This parser is based on the recursive descent paradigm, but the underlying grammar
+ * is not explicitely defined.
+ * <p/>
+ * Resources on ASCII property list format:
+ * <ul>
+ * <li><a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html>
+ * Property List Programming Guide - Old-Style ASCII Property Lists
+ * </a></li>
+ * <li><a href="http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html">
+ * GnuStep - NSPropertyListSerialization class documentation
+ * </a></li>
+ * </ul>
+ *
+ * @author Daniel Dreibrodt
+ */
+public class ASCIIPropertyListParser {
+
+ /**
+ * Parses an ASCII property list file.
+ *
+ * @param f The ASCII property list file.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(File f) throws IOException, ParseException {
+ return parse(new FileInputStream(f));
+ }
+
+ /**
+ * Parses an ASCII property list from an input stream.
+ *
+ * @param in The input stream that points to the property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(InputStream in) throws ParseException, IOException {
+ byte[] buf = PropertyListParser.readAll(in);
+ in.close();
+ return parse(buf);
+ }
+
+ /**
+ * Parses an ASCII property list from a byte array.
+ *
+ * @param bytes The ASCII property list data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(byte[] bytes) throws ParseException {
+ ASCIIPropertyListParser parser = new ASCIIPropertyListParser(bytes);
+ return parser.parse();
+ }
+
+ public static final char WHITESPACE_SPACE = ' ';
+ public static final char WHITESPACE_TAB = '\t';
+ public static final char WHITESPACE_NEWLINE = '\n';
+ public static final char WHITESPACE_CARRIAGE_RETURN = '\r';
+
+ public static final char ARRAY_BEGIN_TOKEN = '(';
+ public static final char ARRAY_END_TOKEN = ')';
+ public static final char ARRAY_ITEM_DELIMITER_TOKEN = ',';
+
+ public static final char DICTIONARY_BEGIN_TOKEN = '{';
+ public static final char DICTIONARY_END_TOKEN = '}';
+ public static final char DICTIONARY_ASSIGN_TOKEN = '=';
+ public static final char DICTIONARY_ITEM_DELIMITER_TOKEN = ';';
+
+ public static final char QUOTEDSTRING_BEGIN_TOKEN = '"';
+ public static final char QUOTEDSTRING_END_TOKEN = '"';
+ public static final char QUOTEDSTRING_ESCAPE_TOKEN = '\\';
+
+ public static final char DATA_BEGIN_TOKEN = '<';
+ public static final char DATA_END_TOKEN = '>';
+
+ public static final char DATA_GSOBJECT_BEGIN_TOKEN = '*';
+ public static final char DATA_GSDATE_BEGIN_TOKEN = 'D';
+ public static final char DATA_GSBOOL_BEGIN_TOKEN = 'B';
+ public static final char DATA_GSBOOL_TRUE_TOKEN = 'Y';
+ public static final char DATA_GSBOOL_FALSE_TOKEN = 'N';
+ public static final char DATA_GSINT_BEGIN_TOKEN = 'I';
+ public static final char DATA_GSREAL_BEGIN_TOKEN = 'R';
+
+ public static final char DATE_DATE_FIELD_DELIMITER = '-';
+ public static final char DATE_TIME_FIELD_DELIMITER = ':';
+ public static final char DATE_GS_DATE_TIME_DELIMITER = ' ';
+ public static final char DATE_APPLE_DATE_TIME_DELIMITER = 'T';
+ public static final char DATE_APPLE_END_TOKEN = 'Z';
+
+ public static final char COMMENT_BEGIN_TOKEN = '/';
+ public static final char MULTILINE_COMMENT_SECOND_TOKEN = '*';
+ public static final char SINGLELINE_COMMENT_SECOND_TOKEN = '/';
+ public static final char MULTILINE_COMMENT_END_TOKEN = '/';
+
+ /**
+ * Property list source data
+ */
+ private byte[] data;
+ /**
+ * Current parsing index
+ */
+ private int index;
+
+ /**
+ * Only allow subclasses to change instantiation.
+ */
+ protected ASCIIPropertyListParser() {
+
+ }
+
+ /**
+ * Creates a new parser for the given property list content.
+ *
+ * @param propertyListContent The content of the property list that is to be parsed.
+ */
+ private ASCIIPropertyListParser(byte[] propertyListContent) {
+ data = propertyListContent;
+ }
+
+ /**
+ * Checks whether the given sequence of symbols can be accepted.
+ *
+ * @param sequence The sequence of tokens to look for.
+ * @return Whether the given tokens occur at the current parsing position.
+ */
+ private boolean acceptSequence(char... sequence) {
+ for (int i = 0; i < sequence.length; i++) {
+ if (data[index + i] != sequence[i])
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the given symbols can be accepted, that is, if one
+ * of the given symbols is found at the current parsing position.
+ *
+ * @param acceptableSymbols The symbols to check.
+ * @return Whether one of the symbols can be accepted or not.
+ */
+ private boolean accept(char... acceptableSymbols) {
+ boolean symbolPresent = false;
+ for (char c : acceptableSymbols) {
+ if (data[index] == c)
+ symbolPresent = true;
+ }
+ return symbolPresent;
+ }
+
+ /**
+ * Checks whether the given symbol can be accepted, that is, if
+ * the given symbols is found at the current parsing position.
+ *
+ * @param acceptableSymbol The symbol to check.
+ * @return Whether the symbol can be accepted or not.
+ */
+ private boolean accept(char acceptableSymbol) {
+ return data[index] == acceptableSymbol;
+ }
+
+ /**
+ * Expects the input to have one of the given symbols at the current parsing position.
+ *
+ * @param expectedSymbols The expected symbols.
+ * @throws ParseException If none of the expected symbols could be found.
+ */
+ private void expect(char... expectedSymbols) throws ParseException {
+ if (!accept(expectedSymbols)) {
+ String excString = "Expected '" + expectedSymbols[0] + "'";
+ for (int i = 1; i < expectedSymbols.length; i++) {
+ excString += " or '" + expectedSymbols[i] + "'";
+ }
+ excString += " but found '" + (char) data[index] + "'";
+ throw new ParseException(excString, index);
+ }
+ }
+
+ /**
+ * Expects the input to have the given symbol at the current parsing position.
+ *
+ * @param expectedSymbol The expected symbol.
+ * @throws ParseException If the expected symbol could be found.
+ */
+ private void expect(char expectedSymbol) throws ParseException {
+ if (!accept(expectedSymbol))
+ throw new ParseException("Expected '" + expectedSymbol + "' but found '" + (char) data[index] + "'", index);
+ }
+
+ /**
+ * Reads an expected symbol.
+ *
+ * @param symbol The symbol to read.
+ * @throws ParseException If the expected symbol could not be read.
+ */
+ private void read(char symbol) throws ParseException {
+ expect(symbol);
+ index++;
+ }
+
+ /**
+ * Skips the current symbol.
+ */
+ private void skip() {
+ index++;
+ }
+
+ /**
+ * Skips several symbols
+ *
+ * @param numSymbols The amount of symbols to skip.
+ */
+ private void skip(int numSymbols) {
+ index += numSymbols;
+ }
+
+ /**
+ * Skips all whitespaces and comments from the current parsing position onward.
+ */
+ private void skipWhitespacesAndComments() {
+ boolean commentSkipped;
+ do {
+ commentSkipped = false;
+
+ //Skip whitespaces
+ while (accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) {
+ skip();
+ }
+
+ //Skip single line comments "//..."
+ if (acceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) {
+ skip(2);
+ readInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE);
+ commentSkipped = true;
+ }
+ //Skip multi line comments "/* ... */"
+ else if (acceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN)) {
+ skip(2);
+ while (true) {
+ if (acceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN)) {
+ skip(2);
+ break;
+ }
+ skip();
+ }
+ commentSkipped = true;
+ }
+ }
+ while (commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again
+ }
+
+ private String toUtf8String(ByteArrayOutputStream stream) {
+ try {
+ return stream.toString("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Reads input until one of the given symbols is found.
+ *
+ * @param symbols The symbols that can occur after the string to read.
+ * @return The input until one the given symbols.
+ */
+ private String readInputUntil(char... symbols) {
+ ByteArrayOutputStream stringBytes = new ByteArrayOutputStream();
+ while (!accept(symbols)) {
+ stringBytes.write(data[index]);
+ skip();
+ }
+ return toUtf8String(stringBytes);
+ }
+
+ /**
+ * Reads input until the given symbol is found.
+ *
+ * @param symbol The symbol that can occur after the string to read.
+ * @return The input until the given symbol.
+ */
+ private String readInputUntil(char symbol) {
+ ByteArrayOutputStream stringBytes = new ByteArrayOutputStream();
+ while (!accept(symbol)) {
+ stringBytes.write(data[index]);
+ skip();
+ }
+ return toUtf8String(stringBytes);
+ }
+
+ /**
+ * Parses the property list from the beginning and returns the root object
+ * of the property list.
+ *
+ * @return The root object of the property list. This can either be a NSDictionary or a NSArray.
+ * @throws ParseException When an error occured during parsing
+ */
+ public NSObject parse() throws ParseException {
+ index = 0;
+ skipWhitespacesAndComments();
+ expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN);
+ try {
+ return parseObject();
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ throw new ParseException("Reached end of input unexpectedly.", index);
+ }
+ }
+
+ /**
+ * Parses the NSObject found at the current position in the property list
+ * data stream.
+ *
+ * @return The parsed NSObject.
+ * @see ASCIIPropertyListParser#index
+ */
+ private NSObject parseObject() throws ParseException {
+ switch (data[index]) {
+ case ARRAY_BEGIN_TOKEN: {
+ return parseArray();
+ }
+ case DICTIONARY_BEGIN_TOKEN: {
+ return parseDictionary();
+ }
+ case DATA_BEGIN_TOKEN: {
+ return parseData();
+ }
+ case QUOTEDSTRING_BEGIN_TOKEN: {
+ String quotedString = parseQuotedString();
+ //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found
+ if (quotedString.length() == 20 && quotedString.charAt(4) == DATE_DATE_FIELD_DELIMITER) {
+ try {
+ return new NSDate(quotedString);
+ } catch (Exception ex) {
+ //not a date? --> return string
+ return new NSString(quotedString);
+ }
+ } else {
+ return new NSString(quotedString);
+ }
+ }
+ default: {
+ //0-9
+ if (data[index] > 0x2F && data[index] < 0x3A) {
+ //could be a date or just a string
+ return parseDateString();
+ } else {
+ //non-numerical -> string or boolean
+ String parsedString = parseString();
+ return new NSString(parsedString);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses an array from the current parsing position.
+ * The prerequisite for calling this method is, that an array begin token has been read.
+ *
+ * @return The array found at the parsing position.
+ */
+ private NSArray parseArray() throws ParseException {
+ //Skip begin token
+ skip();
+ skipWhitespacesAndComments();
+ List<NSObject> objects = new LinkedList<NSObject>();
+ while (!accept(ARRAY_END_TOKEN)) {
+ objects.add(parseObject());
+ skipWhitespacesAndComments();
+ if (accept(ARRAY_ITEM_DELIMITER_TOKEN)) {
+ skip();
+ } else {
+ break; //must have reached end of array
+ }
+ skipWhitespacesAndComments();
+ }
+ //parse end token
+ read(ARRAY_END_TOKEN);
+ return new NSArray(objects.toArray(new NSObject[objects.size()]));
+ }
+
+ /**
+ * Parses a dictionary from the current parsing position.
+ * The prerequisite for calling this method is, that a dictionary begin token has been read.
+ *
+ * @return The dictionary found at the parsing position.
+ */
+ private NSDictionary parseDictionary() throws ParseException {
+ //Skip begin token
+ skip();
+ skipWhitespacesAndComments();
+ NSDictionary dict = new NSDictionary();
+ while (!accept(DICTIONARY_END_TOKEN)) {
+ //Parse key
+ String keyString;
+ if (accept(QUOTEDSTRING_BEGIN_TOKEN)) {
+ keyString = parseQuotedString();
+ } else {
+ keyString = parseString();
+ }
+ skipWhitespacesAndComments();
+
+ //Parse assign token
+ read(DICTIONARY_ASSIGN_TOKEN);
+ skipWhitespacesAndComments();
+
+ NSObject object = parseObject();
+ dict.put(keyString, object);
+ skipWhitespacesAndComments();
+ read(DICTIONARY_ITEM_DELIMITER_TOKEN);
+ skipWhitespacesAndComments();
+ }
+ //skip end token
+ skip();
+ return dict;
+ }
+
+ /**
+ * Parses a data object from the current parsing position.
+ * This can either be a NSData object or a GnuStep NSNumber or NSDate.
+ * The prerequisite for calling this method is, that a data begin token has been read.
+ *
+ * @return The data object found at the parsing position.
+ */
+ private NSObject parseData() throws ParseException {
+ NSObject obj = null;
+ //Skip begin token
+ skip();
+ if (accept(DATA_GSOBJECT_BEGIN_TOKEN)) {
+ skip();
+ expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN);
+ if (accept(DATA_GSBOOL_BEGIN_TOKEN)) {
+ //Boolean
+ skip();
+ expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN);
+ if (accept(DATA_GSBOOL_TRUE_TOKEN)) {
+ obj = new NSNumber(true);
+ } else {
+ obj = new NSNumber(false);
+ }
+ //Skip the parsed boolean token
+ skip();
+ } else if (accept(DATA_GSDATE_BEGIN_TOKEN)) {
+ //Date
+ skip();
+ String dateString = readInputUntil(DATA_END_TOKEN);
+ obj = new NSDate(dateString);
+ } else if (accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) {
+ //Number
+ skip();
+ String numberString = readInputUntil(DATA_END_TOKEN);
+ obj = new NSNumber(numberString);
+ }
+ //parse data end token
+ read(DATA_END_TOKEN);
+ } else {
+ String dataString = readInputUntil(DATA_END_TOKEN);
+ dataString = dataString.replaceAll("\\s+", "");
+
+ int numBytes = dataString.length() / 2;
+ byte[] bytes = new byte[numBytes];
+ for (int i = 0; i < bytes.length; i++) {
+ String byteString = dataString.substring(i * 2, i * 2 + 2);
+ int byteValue = Integer.parseInt(byteString, 16);
+ bytes[i] = (byte) byteValue;
+ }
+ obj = new NSData(bytes);
+
+ //skip end token
+ skip();
+ }
+
+ return obj;
+ }
+
+ /**
+ * Attempts to parse a plain string as a date if possible.
+ *
+ * @return A NSDate if the string represents such an object. Otherwise a NSString is returned.
+ */
+ private NSObject parseDateString() {
+ String numericalString = parseString();
+ if (numericalString.length() > 4 && numericalString.charAt(4) == DATE_DATE_FIELD_DELIMITER) {
+ try {
+ return new NSDate(numericalString);
+ } catch(Exception ex) {
+ //An exception occurs if the string is not a date but just a string
+ }
+ }
+ return new NSString(numericalString);
+ }
+
+ /**
+ * Parses a plain string from the current parsing position.
+ * The string is made up of all characters to the next whitespace, delimiter token or assignment token.
+ *
+ * @return The string found at the current parsing position.
+ */
+ private String parseString() {
+ return readInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE, WHITESPACE_CARRIAGE_RETURN,
+ ARRAY_ITEM_DELIMITER_TOKEN, DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN, ARRAY_END_TOKEN);
+ }
+
+ /**
+ * Parses a quoted string from the current parsing position.
+ * The prerequisite for calling this method is, that a quoted string begin token has been read.
+ *
+ * @return The quoted string found at the parsing method with all special characters unescaped.
+ * @throws ParseException If an error occured during parsing.
+ */
+ private String parseQuotedString() throws ParseException {
+ //Skip begin token
+ skip();
+ ByteArrayOutputStream quotedString = new ByteArrayOutputStream();
+ boolean unescapedBackslash = true;
+ //Read from opening quotation marks to closing quotation marks and skip escaped quotation marks
+ while (data[index] != QUOTEDSTRING_END_TOKEN || (data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash)) {
+ quotedString.write(data[index]);
+ if (accept(QUOTEDSTRING_ESCAPE_TOKEN)) {
+ unescapedBackslash = !(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash);
+ }
+ skip();
+ }
+ String unescapedString;
+ try {
+ unescapedString = parseQuotedString(toUtf8String(quotedString));
+ } catch (Exception ex) {
+ throw new ParseException("The quoted string could not be parsed.", index);
+ }
+ //skip end token
+ skip();
+ return unescapedString;
+ }
+
+ /**
+ * Used to encode the parsed strings
+ */
+ private static CharsetEncoder asciiEncoder;
+
+ /**
+ * Parses a string according to the format specified for ASCII property lists.
+ * Such strings can contain escape sequences which are unescaped in this method.
+ *
+ * @param s The escaped string according to the ASCII property list format, without leading and trailing quotation marks.
+ * @return The unescaped string in UTF-8 or ASCII format, depending on the contained characters.
+ * @throws Exception If the string could not be properly parsed.
+ */
+ public static synchronized String parseQuotedString(String s) throws UnsupportedEncodingException, CharacterCodingException {
+ StringBuilder parsed = new StringBuilder();
+ StringCharacterIterator iterator = new StringCharacterIterator(s);
+ char c = iterator.current();
+
+ while (iterator.getIndex() < iterator.getEndIndex()) {
+ switch (c) {
+ case '\\': { //An escaped sequence is following
+ parsed.append(parseEscapedSequence(iterator));
+ break;
+ }
+ default: {
+ parsed.append(c);
+ break;
+ }
+ }
+ c = iterator.next();
+ }
+ return parsed.toString();
+ }
+
+ /**
+ * Unescapes an escaped character sequence, e.g. \\u00FC.
+ *
+ * @param iterator The string character iterator pointing to the first character after the backslash
+ * @return The unescaped character
+ */
+ private static char parseEscapedSequence(StringCharacterIterator iterator) {
+ char c = iterator.next();
+ if (c == 'b') {
+ return '\b';
+ } else if (c == 'n') {
+ return '\n';
+ } else if (c == 'r') {
+ return '\r';
+ } else if (c == 't') {
+ return '\t';
+ } else if (c == 'U' || c == 'u') {
+ //4 digit hex Unicode value
+ String byte1 = "";
+ byte1 += iterator.next();
+ byte1 += iterator.next();
+ String byte2 = "";
+ byte2 += iterator.next();
+ byte2 += iterator.next();
+ return (char) ((Integer.parseInt(byte1, 16) << 8) + Integer.parseInt(byte2, 16));
+ } else if ((c >= '0') && (c <= '7')) {
+ //3 digit octal ASCII value
+ String num = "";
+ num += c;
+ num += iterator.next();
+ num += iterator.next();
+ return (char) Integer.parseInt(num, 8);
+ } else {
+ // Possibly something that needn't be escaped, but we should accept it
+ // it anyway for consistency with Apple tools.
+ return c;
+ }
+ }
+
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/Base64.java b/third_party/java/dd_plist/java/com/dd/plist/Base64.java
new file mode 100644
index 0000000000..8a20f43891
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/Base64.java
@@ -0,0 +1,2124 @@
+/**
+ * plist - An open source library to parse and generate property lists
+ *
+ * Copyright (C) 2012 Daniel Dreibrodt
+ *
+ * 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;
+
+/**
+ * <p>Encodes and decodes to and from Base64 notation.</p>
+ * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
+ * <p/>
+ * <p>Example:</p>
+ * <p/>
+ * <code>String encoded = Base64.encode( myByteArray );</code>
+ * <br />
+ * <code>byte[] myByteArray = Base64.decode( encoded );</code>
+ * <p/>
+ * <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass
+ * several pieces of information to the encoder. In the "higher level" methods such as
+ * encodeBytes( bytes, options ) the options parameter can be used to indicate such
+ * things as first gzipping the bytes before encoding them, not inserting linefeeds,
+ * and encoding using the URL-safe and Ordered dialects.</p>
+ * <p/>
+ * <p>Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>,
+ * Section 2.1, implementations should not add line feeds unless explicitly told
+ * to do so. I've got Base64 set to this behavior now, although earlier versions
+ * broke lines by default.</p>
+ * <p/>
+ * <p>The constants defined in Base64 can be OR-ed together to combine options, so you
+ * might make a call like this:</p>
+ * <p/>
+ * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code>
+ * <p>to compress the data before encoding it and then making the output have newline characters.</p>
+ * <p>Also...</p>
+ * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code>
+ * <p/>
+ * <p/>
+ * <p/>
+ * <p>
+ * Change Log:
+ * </p>
+ * <ul>
+ * <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the
+ * value 01111111, which is an invalid base 64 character but should not
+ * throw an ArrayIndexOutOfBoundsException either. Led to discovery of
+ * mishandling (or potential for better handling) of other bad input
+ * characters. You should now get an IOException if you try decoding
+ * something that has bad characters in it.</li>
+ * <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded
+ * string ended in the last column; the buffer was not properly shrunk and
+ * contained an extra (null) byte that made it into the string.</li>
+ * <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size
+ * was wrong for files of size 31, 34, and 37 bytes.</li>
+ * <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing
+ * the Base64.B64OutputStream closed the Base64 encoding (by padding with equals
+ * signs) too soon. Also added an option to suppress the automatic decoding
+ * of gzipped streams. Also added experimental support for specifying a
+ * class loader when using the
+ * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)}
+ * method.</li>
+ * <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java
+ * footprint with its CharEncoders and so forth. Fixed some javadocs that were
+ * inconsistent. Removed imports and specified things like java.io.IOException
+ * explicitly inline.</li>
+ * <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the
+ * final encoded data will be so that the code doesn't have to create two output
+ * arrays: an oversized initial one and then a final, exact-sized one. Big win
+ * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not
+ * using the gzip options which uses a different mechanism with streams and stuff).</li>
+ * <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some
+ * similar helper methods to be more efficient with memory by not returning a
+ * String but just a byte array.</li>
+ * <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments
+ * and bug fixes queued up and finally executed. Thanks to everyone who sent
+ * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else.
+ * Much bad coding was cleaned up including throwing exceptions where necessary
+ * instead of returning null values or something similar. Here are some changes
+ * that may affect you:
+ * <ul>
+ * <li><em>Does not break lines, by default.</em> This is to keep in compliance with
+ * <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li>
+ * <li><em>Throws exceptions instead of returning null values.</em> Because some operations
+ * (especially those that may permit the GZIP option) use IO streams, there
+ * is a possiblity of an java.io.IOException being thrown. After some discussion and
+ * thought, I've changed the behavior of the methods to throw java.io.IOExceptions
+ * rather than return null if ever there's an error. I think this is more
+ * appropriate, though it will require some changes to your code. Sorry,
+ * it should have been done this way to begin with.</li>
+ * <li><em>Removed all references to System.out, System.err, and the like.</em>
+ * Shame on me. All I can say is sorry they were ever there.</li>
+ * <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed
+ * such as when passed arrays are null or offsets are invalid.</li>
+ * <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings.
+ * This was especially annoying before for people who were thorough in their
+ * own projects and then had gobs of javadoc warnings on this file.</li>
+ * </ul>
+ * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug
+ * when using very small files (~&lt; 40 bytes).</li>
+ * <li>v2.2 - Added some helper methods for encoding/decoding directly from
+ * one file to the next. Also added a main() method to support command line
+ * encoding/decoding from one file to the next. Also added these Base64 dialects:
+ * <ol>
+ * <li>The default is RFC3548 format.</li>
+ * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
+ * URL and file name friendly format as described in Section 4 of RFC3548.
+ * http://www.faqs.org/rfcs/rfc3548.html</li>
+ * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
+ * URL and file name friendly format that preserves lexical ordering as described
+ * in http://www.faqs.org/qa/rfcc-1940.html</li>
+ * </ol>
+ * Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a>
+ * for contributing the new Base64 dialects.
+ * </li>
+ * <p/>
+ * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
+ * some convenience methods for reading and writing to and from files.</li>
+ * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
+ * with other encodings (like EBCDIC).</li>
+ * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
+ * encoded data was a single byte.</li>
+ * <li>v2.0 - I got rid of methods that used booleans to set options.
+ * Now everything is more consolidated and cleaner. The code now detects
+ * when data that's being decoded is gzip-compressed and will decompress it
+ * automatically. Generally things are cleaner. You'll probably have to
+ * change some method calls that you were making to support the new
+ * options format (<tt>int</tt>s that you "OR" together).</li>
+ * <li>v1.5.1 - Fixed bug when decompressing and decoding to a
+ * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
+ * Added the ability to "suspend" encoding in the Output Stream so
+ * you can turn on and off the encoding if you need to embed base64
+ * data in an otherwise "normal" stream (like an XML file).</li>
+ * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
+ * This helps when using GZIP streams.
+ * Added the ability to GZip-compress objects before encoding them.</li>
+ * <li>v1.4 - Added helper methods to read/write files.</li>
+ * <li>v1.3.6 - Fixed B64OutputStream.flush() so that 'position' is reset.</li>
+ * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
+ * where last buffer being read, if not completely full, was not returned.</li>
+ * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
+ * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
+ * </ul>
+ * <p/>
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.3.7
+ */
+public class Base64 {
+
+/* ******** P U B L I C F I E L D S ******** */
+
+
+ /**
+ * No options specified. Value is zero.
+ */
+ public final static int NO_OPTIONS = 0;
+
+ /**
+ * Specify encoding in first bit. Value is one.
+ */
+ public final static int ENCODE = 1;
+
+
+ /**
+ * Specify decoding in first bit. Value is zero.
+ */
+ public final static int DECODE = 0;
+
+
+ /**
+ * Specify that data should be gzip-compressed in second bit. Value is two.
+ */
+ public final static int GZIP = 2;
+
+ /**
+ * Specify that gzipped data should <em>not</em> be automatically gunzipped.
+ */
+ public final static int DONT_GUNZIP = 4;
+
+
+ /**
+ * Do break lines when encoding. Value is 8.
+ */
+ public final static int DO_BREAK_LINES = 8;
+
+ /**
+ * Encode using Base64-like encoding that is URL- and Filename-safe as described
+ * in Section 4 of RFC3548:
+ * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+ * It is important to note that data encoded this way is <em>not</em> officially valid Base64,
+ * or at the very least should not be called Base64 without also specifying that is
+ * was encoded using the URL- and Filename-safe dialect.
+ */
+ public final static int URL_SAFE = 16;
+
+
+ /**
+ * Encode using the special "ordered" dialect of Base64 described here:
+ * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+ */
+ public final static int ORDERED = 32;
+
+
+/* ******** P R I V A T E F I E L D S ******** */
+
+
+ /**
+ * Maximum line length (76) of Base64 output.
+ */
+ private final static int MAX_LINE_LENGTH = 76;
+
+
+ /**
+ * The equals sign (=) as a byte.
+ */
+ private final static byte EQUALS_SIGN = (byte) '=';
+
+
+ /**
+ * The new line character (\n) as a byte.
+ */
+ private final static byte NEW_LINE = (byte) '\n';
+
+
+ /**
+ * Preferred encoding.
+ */
+ private final static String PREFERRED_ENCODING = "US-ASCII";
+
+
+ private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+ private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+
+
+/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * The 64 valid Base64 values.
+ */
+ /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
+ private final static byte[] _STANDARD_ALPHABET = {
+ (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
+ (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
+ (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g',
+ (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
+ (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
+ (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
+ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
+ (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'
+ };
+
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ */
+ private final static byte[] _STANDARD_DECODABET = {
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
+ };
+
+
+/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548:
+ * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
+ * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash."
+ */
+ private final static byte[] _URL_SAFE_ALPHABET = {
+ (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
+ (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
+ (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g',
+ (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
+ (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
+ (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
+ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
+ (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_'
+ };
+
+ /**
+ * Used in decoding URL- and Filename-safe dialects of Base64.
+ */
+ private final static byte[] _URL_SAFE_DECODABET = {
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 62, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91 - 94
+ 63, // Underscore at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
+ };
+
+
+
+/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */
+
+ /**
+ * I don't get the point of this technique, but someone requested it,
+ * and it is described here:
+ * <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+ */
+ private final static byte[] _ORDERED_ALPHABET = {
+ (byte) '-',
+ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
+ (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9',
+ (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
+ (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
+ (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) '_',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g',
+ (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
+ (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
+ (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z'
+ };
+
+ /**
+ * Used in decoding the "ordered" dialect of Base64.
+ */
+ private final static byte[] _ORDERED_DECODABET = {
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ -9, // Plus sign at decimal 43
+ -9, // Decimal 44
+ 0, // Minus sign at decimal 45
+ -9, // Decimal 46
+ -9, // Slash at decimal 47
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M'
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z'
+ -9, -9, -9, -9, // Decimal 91 - 94
+ 37, // Underscore at decimal 95
+ -9, // Decimal 96
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm'
+ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
+ };
+
+
+/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */
+
+
+ /**
+ * Returns one of the _SOMETHING_ALPHABET byte arrays depending on
+ * the options specified.
+ * It's possible, though silly, to specify ORDERED <b>and</b> URLSAFE
+ * in which case one of them will be picked, though there is
+ * no guarantee as to which one will be picked.
+ */
+ private static byte[] getAlphabet(int options) {
+ if ((options & URL_SAFE) == URL_SAFE) {
+ return _URL_SAFE_ALPHABET;
+ } else if ((options & ORDERED) == ORDERED) {
+ return _ORDERED_ALPHABET;
+ } else {
+ return _STANDARD_ALPHABET;
+ }
+ } // end getAlphabet
+
+
+ /**
+ * Returns one of the _SOMETHING_DECODABET byte arrays depending on
+ * the options specified.
+ * It's possible, though silly, to specify ORDERED and URL_SAFE
+ * in which case one of them will be picked, though there is
+ * no guarantee as to which one will be picked.
+ */
+ private static byte[] getDecodabet(int options) {
+ if ((options & URL_SAFE) == URL_SAFE) {
+ return _URL_SAFE_DECODABET;
+ } else if ((options & ORDERED) == ORDERED) {
+ return _ORDERED_DECODABET;
+ } else {
+ return _STANDARD_DECODABET;
+ }
+ } // end getAlphabet
+
+
+ /**
+ * Defeats instantiation.
+ */
+ private Base64() {
+ }
+
+
+
+
+/* ******** E N C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Encodes up to the first three bytes of array <var>threeBytes</var>
+ * and returns a four-byte array in Base64 notation.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ * The array <var>threeBytes</var> needs only be as big as
+ * <var>numSigBytes</var>.
+ * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+ *
+ * @param b4 A reusable byte array to reduce array instantiation
+ * @param threeBytes the array to convert
+ * @param numSigBytes the number of significant bytes in your array
+ * @return four byte array in Base64 notation.
+ * @since 1.5.1
+ */
+ private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) {
+ encode3to4(threeBytes, 0, numSigBytes, b4, 0, options);
+ return b4;
+ } // end encode3to4
+
+
+ /**
+ * <p>Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accomodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.</p>
+ * <p>This is the lowest level of the encoding methods with
+ * all possible parameters.</p>
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(
+ byte[] source, int srcOffset, int numSigBytes,
+ byte[] destination, int destOffset, int options) {
+
+ byte[] ALPHABET = getAlphabet(options);
+
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index ALPHABET
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
+ | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
+ | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
+ return destination;
+
+ case 2:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+
+ case 1:
+ destination[destOffset] = ALPHABET[(inBuff >>> 18)];
+ destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+
+ /**
+ * Performs Base64 encoding on the <code>raw</code> ByteBuffer,
+ * writing it to the <code>encoded</code> ByteBuffer.
+ * This is an experimental feature. Currently it does not
+ * pass along any options (such as {@link #DO_BREAK_LINES}
+ * or {@link #GZIP}.
+ *
+ * @param raw input buffer
+ * @param encoded output buffer
+ * @since 2.3
+ */
+ public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) {
+ byte[] raw3 = new byte[3];
+ byte[] enc4 = new byte[4];
+
+ while (raw.hasRemaining()) {
+ int rem = Math.min(3, raw.remaining());
+ raw.get(raw3, 0, rem);
+ Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
+ encoded.put(enc4);
+ } // end input remaining
+ }
+
+
+ /**
+ * Performs Base64 encoding on the <code>raw</code> ByteBuffer,
+ * writing it to the <code>encoded</code> CharBuffer.
+ * This is an experimental feature. Currently it does not
+ * pass along any options (such as {@link #DO_BREAK_LINES}
+ * or {@link #GZIP}.
+ *
+ * @param raw input buffer
+ * @param encoded output buffer
+ * @since 2.3
+ */
+ public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) {
+ byte[] raw3 = new byte[3];
+ byte[] enc4 = new byte[4];
+
+ while (raw.hasRemaining()) {
+ int rem = Math.min(3, raw.remaining());
+ raw.get(raw3, 0, rem);
+ Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
+ for (int i = 0; i < 4; i++) {
+ encoded.put((char) (enc4[i] & 0xFF));
+ }
+ } // end input remaining
+ }
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object.
+ * <p/>
+ * <p>As of v 2.3, if the object
+ * cannot be serialized or there is another error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned a null value, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ * <p/>
+ * The object is not GZip-compressed before being encoded.
+ *
+ * @param serializableObject The object to encode
+ * @return The Base64-encoded object
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if serializedObject is null
+ * @since 1.4
+ */
+ public static String encodeObject(java.io.Serializable serializableObject)
+ throws java.io.IOException {
+ return encodeObject(serializableObject, NO_OPTIONS);
+ } // end encodeObject
+
+
+ /**
+ * Serializes an object and returns the Base64-encoded
+ * version of that serialized object.
+ * <p/>
+ * <p>As of v 2.3, if the object
+ * cannot be serialized or there is another error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned a null value, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ * <p/>
+ * The object is not GZip-compressed before being encoded.
+ * <p/>
+ * Example options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * </pre>
+ * <p/>
+ * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+ * <p/>
+ * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+ *
+ * @param serializableObject The object to encode
+ * @param options Specified options
+ * @return The Base64-encoded object
+ * @throws java.io.IOException if there is an error
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeObject(java.io.Serializable serializableObject, int options)
+ throws java.io.IOException {
+
+ if (serializableObject == null) {
+ throw new NullPointerException("Cannot serialize a null object.");
+ } // end if: null
+
+ // Streams
+ java.io.ByteArrayOutputStream baos = null;
+ java.io.OutputStream b64os = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+ java.io.ObjectOutputStream oos = null;
+
+
+ try {
+ // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new B64OutputStream(baos, ENCODE | options);
+ if ((options & GZIP) != 0) {
+ // Gzip
+ gzos = new java.util.zip.GZIPOutputStream(b64os);
+ oos = new java.io.ObjectOutputStream(gzos);
+ } else {
+ // Not gzipped
+ oos = new java.io.ObjectOutputStream(b64os);
+ }
+ oos.writeObject(serializableObject);
+ } // end try
+ catch (java.io.IOException e) {
+ // Catch it and then throw it immediately so that
+ // the finally{} block is called for cleanup.
+ throw e;
+ } // end catch
+ finally {
+ try {
+ oos.close();
+ } catch (Exception e) {
+ }
+ try {
+ gzos.close();
+ } catch (Exception e) {
+ }
+ try {
+ b64os.close();
+ } catch (Exception e) {
+ }
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ // Return value according to relevant encoding.
+ try {
+ return new String(baos.toByteArray(), PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue) {
+ // Fall back to some Java default
+ return new String(baos.toByteArray());
+ } // end catch
+
+ } // end encode
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ *
+ * @param source The data to convert
+ * @return The data in Base64-encoded form
+ * @throws NullPointerException if source array is null
+ * @since 1.4
+ */
+ public static String encodeBytes(byte[] source) {
+ // Since we're not going to have the GZIP encoding turned on,
+ // we're not going to have an java.io.IOException thrown, so
+ // we should not force the user to have to catch it.
+ String encoded = null;
+ try {
+ encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : ex.getMessage();
+ } // end catch
+ assert encoded != null;
+ return encoded;
+ } // end encodeBytes
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Example options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+ * <p/>
+ * <p/>
+ * <p>As of v 2.3, if there is an error with the GZIP stream,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned a null value, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param source The data to convert
+ * @param options Specified options
+ * @return The Base64-encoded data as a String
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes(byte[] source, int options) throws java.io.IOException {
+ return encodeBytes(source, 0, source.length, options);
+ } // end encodeBytes
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Does not GZip-compress data.
+ * <p/>
+ * <p>As of v 2.3, if there is an error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned a null value, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @return The Base64-encoded data as a String
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @since 1.4
+ */
+ public static String encodeBytes(byte[] source, int off, int len) {
+ // Since we're not going to have the GZIP encoding turned on,
+ // we're not going to have an java.io.IOException thrown, so
+ // we should not force the user to have to catch it.
+ String encoded = null;
+ try {
+ encoded = encodeBytes(source, off, len, NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : ex.getMessage();
+ } // end catch
+ assert encoded != null;
+ return encoded;
+ } // end encodeBytes
+
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * <p>
+ * Example options:<pre>
+ * GZIP: gzip-compresses object before encoding it.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * <i>Note: Technically, this makes your encoding non-compliant.</i>
+ * </pre>
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+ * <p>
+ * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
+ * <p/>
+ * <p/>
+ * <p>As of v 2.3, if there is an error with the GZIP stream,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned a null value, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ * @return The Base64-encoded data as a String
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
+ byte[] encoded = encodeBytesToBytes(source, off, len, options);
+
+ // Return value according to relevant encoding.
+ try {
+ return new String(encoded, PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uue) {
+ return new String(encoded);
+ } // end catch
+
+ } // end encodeBytes
+
+
+ /**
+ * Similar to {@link #encodeBytes(byte[])} but returns
+ * a byte array instead of instantiating a String. This is more efficient
+ * if you're working with I/O streams and have large data sets to encode.
+ *
+ * @param source The data to convert
+ * @return The Base64-encoded data as a byte[] (of ASCII characters)
+ * @throws NullPointerException if source array is null
+ * @since 2.3.1
+ */
+ public static byte[] encodeBytesToBytes(byte[] source) {
+ byte[] encoded = null;
+ try {
+ encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS);
+ } catch (java.io.IOException ex) {
+ assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+ }
+ return encoded;
+ }
+
+
+ /**
+ * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns
+ * a byte array instead of instantiating a String. This is more efficient
+ * if you're working with I/O streams and have large data sets to encode.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param options Specified options
+ * @return The Base64-encoded data as a String
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if source array is null
+ * @throws IllegalArgumentException if source array, offset, or length are invalid
+ * @see Base64#GZIP
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.3.1
+ */
+ public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException {
+
+ if (source == null) {
+ throw new NullPointerException("Cannot serialize a null array.");
+ } // end if: null
+
+ if (off < 0) {
+ throw new IllegalArgumentException("Cannot have negative offset: " + off);
+ } // end if: off < 0
+
+ if (len < 0) {
+ throw new IllegalArgumentException("Cannot have length offset: " + len);
+ } // end if: len < 0
+
+ if (off + len > source.length) {
+ throw new IllegalArgumentException(
+ String.format("Cannot have offset of %d and length of %d with array of length %d", off, len, source.length));
+ } // end if: off < 0
+
+
+ // Compress?
+ if ((options & GZIP) != 0) {
+ java.io.ByteArrayOutputStream baos = null;
+ java.util.zip.GZIPOutputStream gzos = null;
+ B64OutputStream b64os = null;
+
+ try {
+ // GZip -> Base64 -> ByteArray
+ baos = new java.io.ByteArrayOutputStream();
+ b64os = new B64OutputStream(baos, ENCODE | options);
+ gzos = new java.util.zip.GZIPOutputStream(b64os);
+
+ gzos.write(source, off, len);
+ gzos.close();
+ } // end try
+ catch (java.io.IOException e) {
+ // Catch it and then throw it immediately so that
+ // the finally{} block is called for cleanup.
+ throw e;
+ } // end catch
+ finally {
+ try {
+ gzos.close();
+ } catch (Exception e) {
+ }
+ try {
+ b64os.close();
+ } catch (Exception e) {
+ }
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return baos.toByteArray();
+ } // end if: compress
+
+ // Else, don't compress. Better not to use streams at all then.
+ else {
+ boolean breakLines = (options & DO_BREAK_LINES) != 0;
+
+ //int len43 = len * 4 / 3;
+ //byte[] outBuff = new byte[ ( len43 ) // Main 4:3
+ // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
+ // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
+ // Try to determine more precisely how big the array needs to be.
+ // If we get it right, we don't have to do an array copy, and
+ // we save a bunch of memory.
+ int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding
+ if (breakLines) {
+ encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
+ }
+ byte[] outBuff = new byte[encLen];
+
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+ encode3to4(source, d + off, 3, outBuff, e, options);
+
+ lineLength += 4;
+ if (breakLines && lineLength >= MAX_LINE_LENGTH) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // en dfor: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, options);
+ e += 4;
+ } // end if: some padding needed
+
+
+ // Only resize array if we didn't guess it right.
+ if (e <= outBuff.length - 1) {
+ // If breaking lines and the last byte falls right at
+ // the line length (76 bytes per line), there will be
+ // one extra byte, and the array will need to be resized.
+ // Not too bad of an estimate on array size, I'd say.
+ byte[] finalOut = new byte[e];
+ System.arraycopy(outBuff, 0, finalOut, 0, e);
+ //System.err.println("Having to resize array from " + outBuff.length + " to " + e );
+ return finalOut;
+ } else {
+ //System.err.println("No need to resize array.");
+ return outBuff;
+ }
+
+ } // end else: don't compress
+
+ } // end encodeBytesToBytes
+
+
+
+
+
+/* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accomodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ * <p>This is the lowest level of the decoding methods with
+ * all possible parameters.</p>
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param options alphabet type is pulled from this (standard, url-safe, ordered)
+ * @return the number of decoded bytes converted
+ * @throws NullPointerException if source or destination arrays are null
+ * @throws IllegalArgumentException if srcOffset or destOffset are invalid
+ * or there is not enough room in the array.
+ * @since 1.3
+ */
+ private static int decode4to3(
+ byte[] source, int srcOffset,
+ byte[] destination, int destOffset, int options) {
+
+ // Lots of error checking and exception throwing
+ if (source == null) {
+ throw new NullPointerException("Source array was null.");
+ } // end if
+ if (destination == null) {
+ throw new NullPointerException("Destination array was null.");
+ } // end if
+ if (srcOffset < 0 || srcOffset + 3 >= source.length) {
+ throw new IllegalArgumentException(String.format(
+ "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset));
+ } // end if
+ if (destOffset < 0 || destOffset + 2 >= destination.length) {
+ throw new IllegalArgumentException(String.format(
+ "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset));
+ } // end if
+
+
+ byte[] DECODABET = getDecodabet(options);
+
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+ int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
+ | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ }
+
+ // Example: DkL=
+ else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+ int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
+ | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
+ | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ }
+
+ // Example: DkLE
+ else {
+ // Two ways to do the same thing. Don't know which way I like best.
+ //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+ // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+ // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+ // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+ int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
+ | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
+ | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
+ | ((DECODABET[source[srcOffset + 3]] & 0xFF));
+
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) (outBuff);
+
+ return 3;
+ }
+ } // end decodeToBytes
+
+
+ /**
+ * Low-level access to decoding ASCII characters in
+ * the form of a byte array. <strong>Ignores GUNZIP option, if
+ * it's set.</strong> This is not generally a recommended method,
+ * although it is used internally as part of the decoding process.
+ * Special case: if len = 0, an empty array is returned. Still,
+ * if you need more speed and reduced memory footprint (and aren't
+ * gzipping), consider this method.
+ *
+ * @param source The Base64 encoded data
+ * @return decoded data
+ * @since 2.3.1
+ */
+ public static byte[] decode(byte[] source)
+ throws java.io.IOException {
+ byte[] decoded = null;
+// try {
+ decoded = decode(source, 0, source.length, Base64.NO_OPTIONS);
+// } catch( java.io.IOException ex ) {
+// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
+// }
+ return decoded;
+ }
+
+
+ /**
+ * Low-level access to decoding ASCII characters in
+ * the form of a byte array. <strong>Ignores GUNZIP option, if
+ * it's set.</strong> This is not generally a recommended method,
+ * although it is used internally as part of the decoding process.
+ * Special case: if len = 0, an empty array is returned. Still,
+ * if you need more speed and reduced memory footprint (and aren't
+ * gzipping), consider this method.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @param options Can specify options such as alphabet type to use
+ * @return decoded data
+ * @throws java.io.IOException If bogus characters exist in source data
+ * @since 1.3
+ */
+ public static byte[] decode(byte[] source, int off, int len, int options)
+ throws java.io.IOException {
+
+ // Lots of error checking and exception throwing
+ if (source == null) {
+ throw new NullPointerException("Cannot decode null source array.");
+ } // end if
+ if (off < 0 || off + len > source.length) {
+ throw new IllegalArgumentException(String.format(
+ "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len));
+ } // end if
+
+ if (len == 0) {
+ return new byte[0];
+ } else if (len < 4) {
+ throw new IllegalArgumentException(
+ "Base64-encoded string must have at least four characters, but length specified was " + len);
+ } // end if
+
+ byte[] DECODABET = getDecodabet(options);
+
+ int len34 = len * 3 / 4; // Estimate on array size
+ byte[] outBuff = new byte[len34]; // Upper limit on size of output
+ int outBuffPosn = 0; // Keep track of where we're writing
+
+ byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
+ int b4Posn = 0; // Keep track of four byte input buffer
+ int i = 0; // Source array counter
+ byte sbiDecode = 0; // Special value from DECODABET
+
+ for (i = off; i < off + len; i++) { // Loop through source
+
+ sbiDecode = DECODABET[source[i] & 0xFF];
+
+ // White space, Equals sign, or legit Base64 character
+ // Note the values such as -5 and -9 in the
+ // DECODABETs at the top of the file.
+ if (sbiDecode >= WHITE_SPACE_ENC) {
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ b4[b4Posn++] = source[i]; // Save non-whitespace
+ if (b4Posn > 3) { // Time to decode?
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options);
+ b4Posn = 0;
+
+ // If that was the equals sign, break out of 'for' loop
+ if (source[i] == EQUALS_SIGN) {
+ break;
+ } // end if: equals sign
+ } // end if: quartet built
+ } // end if: equals sign or better
+ } // end if: white space, equals sign or better
+ else {
+ // There's a bad input character in the Base64 stream.
+ throw new java.io.IOException(String.format(
+ "Bad Base64 input character decimal %d in array position %d", ((int) source[i]) & 0xFF, i));
+ } // end else:
+ } // each input character
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ } // end decode
+
+
+ /**
+ * Decodes data from Base64 notation, automatically
+ * detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @return the decoded data
+ * @throws java.io.IOException If there is a problem
+ * @since 1.4
+ */
+ public static byte[] decode(String s) throws java.io.IOException {
+ return decode(s, NO_OPTIONS);
+ }
+
+
+ /**
+ * Decodes data from Base64 notation, automatically
+ * detecting gzip-compressed data and decompressing it.
+ *
+ * @param s the string to decode
+ * @param options encode options such as URL_SAFE
+ * @return the decoded data
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if <tt>s</tt> is null
+ * @since 1.4
+ */
+ public static byte[] decode(String s, int options) throws java.io.IOException {
+
+ if (s == null) {
+ throw new NullPointerException("Input string was null.");
+ } // end if
+
+ byte[] bytes;
+ try {
+ bytes = s.getBytes(PREFERRED_ENCODING);
+ } // end try
+ catch (java.io.UnsupportedEncodingException uee) {
+ bytes = s.getBytes();
+ } // end catch
+ //</change>
+
+ // Decode
+ bytes = decode(bytes, 0, bytes.length, options);
+
+ // Check to see if it's gzip-compressed
+ // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+ boolean dontGunzip = (options & DONT_GUNZIP) != 0;
+ if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) {
+
+ int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+ if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
+ java.io.ByteArrayInputStream bais = null;
+ java.util.zip.GZIPInputStream gzis = null;
+ java.io.ByteArrayOutputStream baos = null;
+ byte[] buffer = new byte[2048];
+ int length = 0;
+
+ try {
+ baos = new java.io.ByteArrayOutputStream();
+ bais = new java.io.ByteArrayInputStream(bytes);
+ gzis = new java.util.zip.GZIPInputStream(bais);
+
+ while ((length = gzis.read(buffer)) >= 0) {
+ baos.write(buffer, 0, length);
+ } // end while: reading input
+
+ // No error? Get new bytes.
+ bytes = baos.toByteArray();
+
+ } // end try
+ catch (java.io.IOException e) {
+ e.printStackTrace();
+ // Just return originally-decoded bytes
+ } // end catch
+ finally {
+ try {
+ baos.close();
+ } catch (Exception e) {
+ }
+ try {
+ gzis.close();
+ } catch (Exception e) {
+ }
+ try {
+ bais.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end if: gzipped
+ } // end if: bytes.length >= 2
+
+ return bytes;
+ } // end decode
+
+
+ /**
+ * Attempts to decode Base64 data and deserialize a Java
+ * Object within. Returns <tt>null</tt> if there was an error.
+ *
+ * @param encodedObject The Base64 data to decode
+ * @return The decoded and deserialized object
+ * @throws NullPointerException if encodedObject is null
+ * @throws java.io.IOException if there is a general error
+ * @throws ClassNotFoundException if the decoded object is of a
+ * class that cannot be found by the JVM
+ * @since 1.5
+ */
+ public static Object decodeToObject(String encodedObject)
+ throws java.io.IOException, java.lang.ClassNotFoundException {
+ return decodeToObject(encodedObject, NO_OPTIONS, null);
+ }
+
+
+ /**
+ * Attempts to decode Base64 data and deserialize a Java
+ * Object within. Returns <tt>null</tt> if there was an error.
+ * If <tt>loader</tt> is not null, it will be the class loader
+ * used when deserializing.
+ *
+ * @param encodedObject The Base64 data to decode
+ * @param options Various parameters related to decoding
+ * @param loader Optional class loader to use in deserializing classes.
+ * @return The decoded and deserialized object
+ * @throws NullPointerException if encodedObject is null
+ * @throws java.io.IOException if there is a general error
+ * @throws ClassNotFoundException if the decoded object is of a
+ * class that cannot be found by the JVM
+ * @since 2.3.4
+ */
+ public static Object decodeToObject(
+ String encodedObject, int options, final ClassLoader loader)
+ throws java.io.IOException, java.lang.ClassNotFoundException {
+
+ // Decode and gunzip if necessary
+ byte[] objBytes = decode(encodedObject, options);
+
+ java.io.ByteArrayInputStream bais = null;
+ java.io.ObjectInputStream ois = null;
+ Object obj = null;
+
+ try {
+ bais = new java.io.ByteArrayInputStream(objBytes);
+
+ // If no custom class loader is provided, use Java's builtin OIS.
+ if (loader == null) {
+ ois = new java.io.ObjectInputStream(bais);
+ } // end if: no loader provided
+
+ // Else make a customized object input stream that uses
+ // the provided class loader.
+ else {
+ ois = new java.io.ObjectInputStream(bais) {
+ @Override
+ public Class<?> resolveClass(java.io.ObjectStreamClass streamClass)
+ throws java.io.IOException, ClassNotFoundException {
+ Class c = Class.forName(streamClass.getName(), false, loader);
+ if (c == null) {
+ return super.resolveClass(streamClass);
+ } else {
+ return c; // Class loader knows of this class.
+ } // end else: not null
+ } // end resolveClass
+ }; // end ois
+ } // end else: no custom class loader
+
+ obj = ois.readObject();
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw in order to execute finally{}
+ } // end catch
+ catch (java.lang.ClassNotFoundException e) {
+ throw e; // Catch and throw in order to execute finally{}
+ } // end catch
+ finally {
+ try {
+ bais.close();
+ } catch (Exception e) {
+ }
+ try {
+ ois.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return obj;
+ } // end decodeObject
+
+
+ /**
+ * Convenience method for encoding data to a file.
+ * <p/>
+ * <p>As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned false, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param dataToEncode byte array of data to encode in base64 form
+ * @param filename Filename for saving encoded data
+ * @throws java.io.IOException if there is an error
+ * @throws NullPointerException if dataToEncode is null
+ * @since 2.1
+ */
+ public static void encodeToFile(byte[] dataToEncode, String filename)
+ throws java.io.IOException {
+
+ if (dataToEncode == null) {
+ throw new NullPointerException("Data to encode was null.");
+ } // end iff
+
+ B64OutputStream bos = null;
+ try {
+ bos = new B64OutputStream(
+ new java.io.FileOutputStream(filename), Base64.ENCODE);
+ bos.write(dataToEncode);
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw to execute finally{} block
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end encodeToFile
+
+
+ /**
+ * Convenience method for decoding data to a file.
+ * <p/>
+ * <p>As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned false, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param dataToDecode Base64-encoded data as a string
+ * @param filename Filename for saving decoded data
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static void decodeToFile(String dataToDecode, String filename)
+ throws java.io.IOException {
+
+ B64OutputStream bos = null;
+ try {
+ bos = new B64OutputStream(
+ new java.io.FileOutputStream(filename), Base64.DECODE);
+ bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and throw to execute finally{} block
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bos.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ } // end decodeToFile
+
+
+ /**
+ * Convenience method for reading a base64-encoded
+ * file and decoding it.
+ * <p/>
+ * <p>As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned false, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param filename Filename for reading encoded data
+ * @return decoded byte array
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static byte[] decodeFromFile(String filename)
+ throws java.io.IOException {
+
+ byte[] decodedData = null;
+ B64InputStream bis = null;
+ try {
+ // Set up some useful variables
+ java.io.File file = new java.io.File(filename);
+ byte[] buffer = null;
+ int length = 0;
+ int numBytes = 0;
+
+ // Check for size of file
+ if (file.length() > Integer.MAX_VALUE) {
+ throw new java.io.IOException("File is too big for this convenience method (" + file.length() + " bytes).");
+ } // end if: file too big for int index
+ buffer = new byte[(int) file.length()];
+
+ // Open a stream
+ bis = new B64InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream(file)), Base64.DECODE);
+
+ // Read until done
+ while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
+ length += numBytes;
+ } // end while
+
+ // Save in a variable to return
+ decodedData = new byte[length];
+ System.arraycopy(buffer, 0, decodedData, 0, length);
+
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bis.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return decodedData;
+ } // end decodeFromFile
+
+
+ /**
+ * Convenience method for reading a binary file
+ * and base64-encoding it.
+ * <p/>
+ * <p>As of v 2.3, if there is a error,
+ * the method will throw an java.io.IOException. <b>This is new to v2.3!</b>
+ * In earlier versions, it just returned false, but
+ * in retrospect that's a pretty poor way to handle it.</p>
+ *
+ * @param filename Filename for reading binary data
+ * @return base64-encoded string
+ * @throws java.io.IOException if there is an error
+ * @since 2.1
+ */
+ public static String encodeFromFile(String filename)
+ throws java.io.IOException {
+
+ String encodedData = null;
+ B64InputStream bis = null;
+ try {
+ // Set up some useful variables
+ java.io.File file = new java.io.File(filename);
+ byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5)
+ int length = 0;
+ int numBytes = 0;
+
+ // Open a stream
+ bis = new B64InputStream(
+ new java.io.BufferedInputStream(
+ new java.io.FileInputStream(file)), Base64.ENCODE);
+
+ // Read until done
+ while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
+ length += numBytes;
+ } // end while
+
+ // Save in a variable to return
+ encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
+
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch: java.io.IOException
+ finally {
+ try {
+ bis.close();
+ } catch (Exception e) {
+ }
+ } // end finally
+
+ return encodedData;
+ } // end encodeFromFile
+
+ /**
+ * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ * @throws java.io.IOException if there is an error
+ * @since 2.2
+ */
+ public static void encodeFileToFile(String infile, String outfile)
+ throws java.io.IOException {
+
+ String encoded = Base64.encodeFromFile(infile);
+ java.io.OutputStream out = null;
+ try {
+ out = new java.io.BufferedOutputStream(
+ new java.io.FileOutputStream(outfile));
+ out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output.
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch
+ finally {
+ try {
+ out.close();
+ } catch (Exception ex) {
+ }
+ } // end finally
+ } // end encodeFileToFile
+
+
+ /**
+ * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
+ *
+ * @param infile Input file
+ * @param outfile Output file
+ * @throws java.io.IOException if there is an error
+ * @since 2.2
+ */
+ public static void decodeFileToFile(String infile, String outfile)
+ throws java.io.IOException {
+
+ byte[] decoded = Base64.decodeFromFile(infile);
+ java.io.OutputStream out = null;
+ try {
+ out = new java.io.BufferedOutputStream(
+ new java.io.FileOutputStream(outfile));
+ out.write(decoded);
+ } // end try
+ catch (java.io.IOException e) {
+ throw e; // Catch and release to execute finally{}
+ } // end catch
+ finally {
+ try {
+ out.close();
+ } catch (Exception ex) {
+ }
+ } // end finally
+ } // end decodeFileToFile
+
+
+ /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
+
+
+ /**
+ * A {@link com.dd.plist.Base64.B64InputStream} will read data from another
+ * <tt>java.io.InputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class B64InputStream extends java.io.FilterInputStream {
+
+ private boolean encode; // Encoding or decoding
+ private int position; // Current position in the buffer
+ private byte[] buffer; // Small buffer holding converted data
+ private int bufferLength; // Length of buffer (3 or 4)
+ private int numSigBytes; // Number of meaningful bytes in the buffer
+ private int lineLength;
+ private boolean breakLines; // Break lines at less than 80 characters
+ private int options; // Record options used to create the stream.
+ private byte[] decodabet; // Local copies to avoid extra method calls
+
+
+ /**
+ * Constructs a {@link com.dd.plist.Base64.B64InputStream} in DECODE mode.
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @since 1.3
+ */
+ public B64InputStream(java.io.InputStream in) {
+ this(in, DECODE);
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link com.dd.plist.Base64.B64InputStream} in
+ * either ENCODE or DECODE mode.
+ * <p/>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DO_BREAK_LINES: break lines at 76 characters
+ * (only meaningful when encoding)</i>
+ * </pre>
+ * <p/>
+ * Example: <code>new Base64.B64InputStream( in, Base64.DECODE )</code>
+ *
+ * @param in the <tt>java.io.InputStream</tt> from which to read data.
+ * @param options Specified options
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DO_BREAK_LINES
+ * @since 2.0
+ */
+ public B64InputStream(java.io.InputStream in, int options) {
+
+ super(in);
+ this.options = options; // Record for later
+ this.breakLines = (options & DO_BREAK_LINES) > 0;
+ this.encode = (options & ENCODE) > 0;
+ this.bufferLength = encode ? 4 : 3;
+ this.buffer = new byte[bufferLength];
+ this.position = -1;
+ this.lineLength = 0;
+ this.decodabet = getDecodabet(options);
+ } // end constructor
+
+ /**
+ * Reads enough of the input stream to convert
+ * to/from Base64 and returns the next byte.
+ *
+ * @return next byte
+ * @since 1.3
+ */
+ @Override
+ public int read() throws java.io.IOException {
+
+ // Do we need to get data?
+ if (position < 0) {
+ if (encode) {
+ byte[] b3 = new byte[3];
+ int numBinaryBytes = 0;
+ for (int i = 0; i < 3; i++) {
+ int b = in.read();
+
+ // If end of stream, b is -1.
+ if (b >= 0) {
+ b3[i] = (byte) b;
+ numBinaryBytes++;
+ } else {
+ break; // out of for loop
+ } // end else: end of stream
+
+ } // end for: each needed input byte
+
+ if (numBinaryBytes > 0) {
+ encode3to4(b3, 0, numBinaryBytes, buffer, 0, options);
+ position = 0;
+ numSigBytes = 4;
+ } // end if: got data
+ else {
+ return -1; // Must be end of stream
+ } // end else
+ } // end if: encoding
+
+ // Else decoding
+ else {
+ byte[] b4 = new byte[4];
+ int i = 0;
+ for (i = 0; i < 4; i++) {
+ // Read four "meaningful" bytes:
+ int b = 0;
+ do {
+ b = in.read();
+ }
+ while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC);
+
+ if (b < 0) {
+ break; // Reads a -1 if end of stream
+ } // end if: end of stream
+
+ b4[i] = (byte) b;
+ } // end for: each needed input byte
+
+ if (i == 4) {
+ numSigBytes = decode4to3(b4, 0, buffer, 0, options);
+ position = 0;
+ } // end if: got four characters
+ else if (i == 0) {
+ return -1;
+ } // end else if: also padded correctly
+ else {
+ // Must have broken out from above.
+ throw new java.io.IOException("Improperly padded Base64 input.");
+ } // end
+
+ } // end else: decode
+ } // end else: get data
+
+ // Got data?
+ if (position >= 0) {
+ // End of relevant data?
+ if ( /*!encode &&*/ position >= numSigBytes) {
+ return -1;
+ } // end if: got data
+
+ if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
+ lineLength = 0;
+ return '\n';
+ } // end if
+ else {
+ lineLength++; // This isn't important when decoding
+ // but throwing an extra "if" seems
+ // just as wasteful.
+
+ int b = buffer[position++];
+
+ if (position >= bufferLength) {
+ position = -1;
+ } // end if: end
+
+ return b & 0xFF; // This is how you "cast" a byte that's
+ // intended to be unsigned.
+ } // end else
+ } // end if: position >= 0
+
+ // Else error
+ else {
+ throw new java.io.IOException("Error in Base64 code reading stream.");
+ } // end else
+ } // end read
+
+
+ /**
+ * Calls {@link #read()} repeatedly until the end of stream
+ * is reached or <var>len</var> bytes are read.
+ * Returns number of bytes read into array or -1 if
+ * end of stream is encountered.
+ *
+ * @param dest array to hold values
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @return bytes read into array or -1 if end of stream is encountered.
+ * @since 1.3
+ */
+ @Override
+ public int read(byte[] dest, int off, int len)
+ throws java.io.IOException {
+ int i;
+ int b;
+ for (i = 0; i < len; i++) {
+ b = read();
+
+ if (b >= 0) {
+ dest[off + i] = (byte) b;
+ } else if (i == 0) {
+ return -1;
+ } else {
+ break; // Out of 'for' loop
+ } // Out of 'for' loop
+ } // end for: each byte read
+ return i;
+ } // end read
+
+ } // end inner class B64InputStream
+
+
+
+
+
+
+ /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
+
+
+ /**
+ * A {@link com.dd.plist.Base64.B64OutputStream} will write data to another
+ * <tt>java.io.OutputStream</tt>, given in the constructor,
+ * and encode/decode to/from Base64 notation on the fly.
+ *
+ * @see Base64
+ * @since 1.3
+ */
+ public static class B64OutputStream extends java.io.FilterOutputStream {
+
+ private boolean encode;
+ private int position;
+ private byte[] buffer;
+ private int bufferLength;
+ private int lineLength;
+ private boolean breakLines;
+ private byte[] b4; // Scratch used in a few places
+ private boolean suspendEncoding;
+ private int options; // Record for later
+ private byte[] decodabet; // Local copies to avoid extra method calls
+
+ /**
+ * Constructs a {@link com.dd.plist.Base64.B64OutputStream} in ENCODE mode.
+ *
+ * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+ * @since 1.3
+ */
+ public B64OutputStream(java.io.OutputStream out) {
+ this(out, ENCODE);
+ } // end constructor
+
+
+ /**
+ * Constructs a {@link com.dd.plist.Base64.B64OutputStream} in
+ * either ENCODE or DECODE mode.
+ * <p/>
+ * Valid options:<pre>
+ * ENCODE or DECODE: Encode or Decode as data is read.
+ * DO_BREAK_LINES: don't break lines at 76 characters
+ * (only meaningful when encoding)</i>
+ * </pre>
+ * <p/>
+ * Example: <code>new Base64.B64OutputStream( out, Base64.ENCODE )</code>
+ *
+ * @param out the <tt>java.io.B64OutputStream</tt> to which data will be written.
+ * @param options Specified options.
+ * @see Base64#ENCODE
+ * @see Base64#DECODE
+ * @see Base64#DO_BREAK_LINES
+ * @since 1.3
+ */
+ public B64OutputStream(java.io.OutputStream out, int options) {
+ super(out);
+ this.breakLines = (options & DO_BREAK_LINES) != 0;
+ this.encode = (options & ENCODE) != 0;
+ this.bufferLength = encode ? 3 : 4;
+ this.buffer = new byte[bufferLength];
+ this.position = 0;
+ this.lineLength = 0;
+ this.suspendEncoding = false;
+ this.b4 = new byte[4];
+ this.options = options;
+ this.decodabet = getDecodabet(options);
+ } // end constructor
+
+
+ /**
+ * Writes the byte to the output stream after
+ * converting to/from Base64 notation.
+ * When encoding, bytes are buffered three
+ * at a time before the output stream actually
+ * gets a write() call.
+ * When decoding, bytes are buffered four
+ * at a time.
+ *
+ * @param theByte the byte to write
+ * @since 1.3
+ */
+ @Override
+ public void write(int theByte)
+ throws java.io.IOException {
+ // Encoding suspended?
+ if (suspendEncoding) {
+ this.out.write(theByte);
+ return;
+ } // end if: supsended
+
+ // Encode?
+ if (encode) {
+ buffer[position++] = (byte) theByte;
+ if (position >= bufferLength) { // Enough to encode.
+
+ this.out.write(encode3to4(b4, buffer, bufferLength, options));
+
+ lineLength += 4;
+ if (breakLines && lineLength >= MAX_LINE_LENGTH) {
+ this.out.write(NEW_LINE);
+ lineLength = 0;
+ } // end if: end of line
+
+ position = 0;
+ } // end if: enough to output
+ } // end if: encoding
+
+ // Else, Decoding
+ else {
+ // Meaningful Base64 character?
+ if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) {
+ buffer[position++] = (byte) theByte;
+ if (position >= bufferLength) { // Enough to output.
+
+ int len = Base64.decode4to3(buffer, 0, b4, 0, options);
+ out.write(b4, 0, len);
+ position = 0;
+ } // end if: enough to output
+ } // end if: meaningful base64 character
+ else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) {
+ throw new java.io.IOException("Invalid character in Base64 data.");
+ } // end else: not white space either
+ } // end else: decoding
+ } // end write
+
+
+ /**
+ * Calls {@link #write(int)} repeatedly until <var>len</var>
+ * bytes are written.
+ *
+ * @param theBytes array from which to read bytes
+ * @param off offset for array
+ * @param len max number of bytes to read into array
+ * @since 1.3
+ */
+ @Override
+ public void write(byte[] theBytes, int off, int len)
+ throws java.io.IOException {
+ // Encoding suspended?
+ if (suspendEncoding) {
+ this.out.write(theBytes, off, len);
+ return;
+ } // end if: supsended
+
+ for (int i = 0; i < len; i++) {
+ write(theBytes[off + i]);
+ } // end for: each byte written
+
+ } // end write
+
+
+ /**
+ * Method added by PHIL. [Thanks, PHIL. -Rob]
+ * This pads the buffer without closing the stream.
+ *
+ * @throws java.io.IOException if there's an error.
+ */
+ public void flushBase64() throws java.io.IOException {
+ if (position > 0) {
+ if (encode) {
+ out.write(encode3to4(b4, buffer, position, options));
+ position = 0;
+ } // end if: encoding
+ else {
+ throw new java.io.IOException("Base64 input not properly padded.");
+ } // end else: decoding
+ } // end if: buffer partially full
+
+ } // end flush
+
+
+ /**
+ * Flushes and closes (I think, in the superclass) the stream.
+ *
+ * @since 1.3
+ */
+ @Override
+ public void close() throws java.io.IOException {
+ // 1. Ensure that pending characters are written
+ flushBase64();
+
+ // 2. Actually close the stream
+ // Base class both flushes and closes.
+ super.close();
+
+ buffer = null;
+ out = null;
+ } // end close
+
+
+ /**
+ * Suspends encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base64-encoded data in a stream.
+ *
+ * @throws java.io.IOException if there's an error flushing
+ * @since 1.5.1
+ */
+ public void suspendEncoding() throws java.io.IOException {
+ flushBase64();
+ this.suspendEncoding = true;
+ } // end suspendEncoding
+
+
+ /**
+ * Resumes encoding of the stream.
+ * May be helpful if you need to embed a piece of
+ * base64-encoded data in a stream.
+ *
+ * @since 1.5.1
+ */
+ public void resumeEncoding() {
+ this.suspendEncoding = false;
+ } // end resumeEncoding
+
+
+ } // end inner class B64OutputStream
+
+
+} // end class Base64
diff --git a/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListParser.java b/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListParser.java
new file mode 100644
index 0000000000..ee2ca7ada6
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListParser.java
@@ -0,0 +1,541 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011-2014 Daniel Dreibrodt
+ *
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+/**
+ * Parses property lists that are in Apple's binary format.
+ * Use this class when you are sure about the format of the property list.
+ * Otherwise use the PropertyListParser class.
+ * <p/>
+ * Parsing is done by calling the static <code>parse</code> methods.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class BinaryPropertyListParser {
+
+ private int majorVersion, minorVersion;
+
+ /**
+ * property list in bytes *
+ */
+ private byte[] bytes;
+ /**
+ * Length of an offset definition in bytes *
+ */
+ private int offsetSize;
+ /**
+ * Length of an object reference in bytes *
+ */
+ private int objectRefSize;
+ /**
+ * Number of objects stored in this property list *
+ */
+ private int numObjects;
+ /**
+ * Reference to the top object of the property list *
+ */
+ private int topObject;
+ /**
+ * Offset of the offset table from the beginning of the file *
+ */
+ private int offsetTableOffset;
+ /**
+ * The table holding the information at which offset each object is found *
+ */
+ private int[] offsetTable;
+
+ /**
+ * Protected constructor so that instantiation is fully controlled by the
+ * static parse methods.
+ *
+ * @see BinaryPropertyListParser#parse(byte[])
+ */
+ protected BinaryPropertyListParser() {
+ /** empty **/
+ }
+
+ /**
+ * Parses a binary property list from a byte array.
+ *
+ * @param data The binary property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(byte[] data) throws IOException, PropertyListFormatException {
+ BinaryPropertyListParser parser = new BinaryPropertyListParser();
+ return parser.doParse(data);
+ }
+
+ /**
+ * Parses a binary property list from a byte array.
+ *
+ * @param data The binary property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ private NSObject doParse(byte[] data) throws IOException, PropertyListFormatException {
+ bytes = data;
+ String magic = new String(copyOfRange(bytes, 0, 8));
+ if (!magic.startsWith("bplist")) {
+ throw new IllegalArgumentException("The given data is no binary property list. Wrong magic bytes: " + magic);
+ }
+
+ majorVersion = magic.charAt(6) - 0x30; //ASCII number
+ minorVersion = magic.charAt(7) - 0x30; //ASCII number
+
+ // 0.0 - OS X Tiger and earlier
+ // 0.1 - Leopard
+ // 0.? - Snow Leopard
+ // 1.5 - Lion
+ // 2.0 - Snow Lion
+
+ if (majorVersion > 0) {
+ throw new IllegalArgumentException("Unsupported binary property list format: v" + majorVersion + "." + minorVersion + ". " +
+ "Version 1.0 and later are not yet supported.");
+ }
+
+ /*
+ * Handle trailer, last 32 bytes of the file
+ */
+ byte[] trailer = copyOfRange(bytes, bytes.length - 32, bytes.length);
+ //6 null bytes (index 0 to 5)
+ offsetSize = (int) parseUnsignedInt(trailer, 6, 7);
+ //System.out.println("offsetSize: "+offsetSize);
+ objectRefSize = (int) parseUnsignedInt(trailer, 7, 8);
+ //System.out.println("objectRefSize: "+objectRefSize);
+ numObjects = (int) parseUnsignedInt(trailer, 8, 16);
+ //System.out.println("numObjects: "+numObjects);
+ topObject = (int) parseUnsignedInt(trailer, 16, 24);
+ //System.out.println("topObject: "+topObject);
+ offsetTableOffset = (int) parseUnsignedInt(trailer, 24, 32);
+ //System.out.println("offsetTableOffset: "+offsetTableOffset);
+
+ /*
+ * Handle offset table
+ */
+ offsetTable = new int[numObjects];
+
+ for (int i = 0; i < numObjects; i++) {
+ byte[] offsetBytes = copyOfRange(bytes, offsetTableOffset + i * offsetSize, offsetTableOffset + (i + 1) * offsetSize);
+ offsetTable[i] = (int) parseUnsignedInt(offsetBytes);
+ /*System.out.print("Offset for Object #"+i+" is "+offsetTable[i]+" [");
+ for(byte b:offsetBytes) System.out.print(Integer.toHexString(b)+" ");
+ System.out.println("]");*/
+ }
+
+ return parseObject(topObject);
+ }
+
+ /**
+ * Parses a binary property list from an input stream.
+ *
+ * @param is The input stream that points to the property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(InputStream is) throws IOException, PropertyListFormatException {
+ //Read all bytes into a list
+ byte[] buf = PropertyListParser.readAll(is);
+ is.close();
+ return parse(buf);
+ }
+
+ /**
+ * Parses a binary property list file.
+ *
+ * @param f The binary property list file
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(File f) throws IOException, PropertyListFormatException {
+ if (f.length() > Runtime.getRuntime().freeMemory()) {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + f.length() + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ return parse(new FileInputStream(f));
+ }
+
+ /**
+ * Parses an object inside the currently parsed binary property list.
+ * For the format specification check
+ * <a href="http://www.opensource.apple.com/source/CF/CF-744/CFBinaryPList.c">
+ * Apple's binary property list parser implementation</a>.
+ *
+ * @param obj The object ID.
+ * @return The parsed object.
+ * @throws java.lang.Exception When an error occurs during parsing.
+ */
+ private NSObject parseObject(int obj) throws IOException, PropertyListFormatException {
+ int offset = offsetTable[obj];
+ byte type = bytes[offset];
+ int objType = (type & 0xF0) >> 4; //First 4 bits
+ int objInfo = (type & 0x0F); //Second 4 bits
+ switch (objType) {
+ case 0x0: {
+ //Simple
+ switch (objInfo) {
+ case 0x0: {
+ //null object (v1.0 and later)
+ return null;
+ }
+ case 0x8: {
+ //false
+ return new NSNumber(false);
+ }
+ case 0x9: {
+ //true
+ return new NSNumber(true);
+ }
+ case 0xC: {
+ //URL with no base URL (v1.0 and later)
+ //TODO
+ break;
+ }
+ case 0xD: {
+ //URL with base URL (v1.0 and later)
+ //TODO
+ break;
+ }
+ case 0xE: {
+ //16-byte UUID (v1.0 and later)
+ //TODO
+ break;
+ }
+ case 0xF: {
+ //filler byte
+ return null;
+ }
+ }
+ break;
+ }
+ case 0x1: {
+ //integer
+ int length = (int) Math.pow(2, objInfo);
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new NSNumber(copyOfRange(bytes, offset + 1, offset + 1 + length), NSNumber.INTEGER);
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0x2: {
+ //real
+ int length = (int) Math.pow(2, objInfo);
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new NSNumber(copyOfRange(bytes, offset + 1, offset + 1 + length), NSNumber.REAL);
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0x3: {
+ //Date
+ if (objInfo != 0x3) {
+ throw new PropertyListFormatException("The given binary property list contains a date object of an unknown type ("+objInfo+")");
+ }
+ return new NSDate(copyOfRange(bytes, offset + 1, offset + 9));
+ }
+ case 0x4: {
+ //Data
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int dataoffset = lenAndoffset[1];
+
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new NSData(copyOfRange(bytes, offset + dataoffset, offset + dataoffset + length));
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0x5: {
+ //ASCII String
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int stroffset = lenAndoffset[1];
+
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new NSString(copyOfRange(bytes, offset + stroffset, offset + stroffset + length), "ASCII");
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0x6: {
+ //UTF-16-BE String
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int stroffset = lenAndoffset[1];
+
+ //length is String length -> to get byte length multiply by 2, as 1 character takes 2 bytes in UTF-16
+ length *= 2;
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new NSString(copyOfRange(bytes, offset + stroffset, offset + stroffset + length), "UTF-16BE");
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0x8: {
+ //UID
+ int length = objInfo + 1;
+ if (length < Runtime.getRuntime().freeMemory()) {
+ return new UID(String.valueOf(obj), copyOfRange(bytes, offset + 1, offset + 1 + length));
+ } else {
+ throw new OutOfMemoryError("To little heap space available! Wanted to read " + length + " bytes, but only " + Runtime.getRuntime().freeMemory() + " are available.");
+ }
+ }
+ case 0xA: {
+ //Array
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int arrayoffset = lenAndoffset[1];
+
+ if (length * objectRefSize > Runtime.getRuntime().freeMemory()) {
+ throw new OutOfMemoryError("To little heap space available!");
+ }
+ NSArray array = new NSArray(length);
+ for (int i = 0; i < length; i++) {
+ int objRef = (int) parseUnsignedInt(copyOfRange(bytes,
+ offset + arrayoffset + i * objectRefSize,
+ offset + arrayoffset + (i + 1) * objectRefSize));
+ array.setValue(i, parseObject(objRef));
+ }
+ return array;
+
+ }
+ case 0xB: {
+ //Ordered set
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int contentOffset = lenAndoffset[1];
+
+ if (length * objectRefSize > Runtime.getRuntime().freeMemory()) {
+ throw new OutOfMemoryError("To little heap space available!");
+ }
+ NSSet set = new NSSet(true);
+ for (int i = 0; i < length; i++) {
+ int objRef = (int) parseUnsignedInt(copyOfRange(bytes,
+ offset + contentOffset + i * objectRefSize,
+ offset + contentOffset + (i + 1) * objectRefSize));
+ set.addObject(parseObject(objRef));
+ }
+ return set;
+ }
+ case 0xC: {
+ //Set
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int contentOffset = lenAndoffset[1];
+
+ if (length * objectRefSize > Runtime.getRuntime().freeMemory()) {
+ throw new OutOfMemoryError("To little heap space available!");
+ }
+ NSSet set = new NSSet();
+ for (int i = 0; i < length; i++) {
+ int objRef = (int) parseUnsignedInt(copyOfRange(bytes,
+ offset + contentOffset + i * objectRefSize,
+ offset + contentOffset + (i + 1) * objectRefSize));
+ set.addObject(parseObject(objRef));
+ }
+ return set;
+ }
+ case 0xD: {
+ //Dictionary
+ int[] lenAndoffset = readLengthAndOffset(objInfo, offset);
+ int length = lenAndoffset[0];
+ int contentOffset = lenAndoffset[1];
+
+ if (length * 2 * objectRefSize > Runtime.getRuntime().freeMemory()) {
+ throw new OutOfMemoryError("To little heap space available!");
+ }
+ //System.out.println("Parsing dictionary #"+obj);
+ NSDictionary dict = new NSDictionary();
+ for (int i = 0; i < length; i++) {
+ int keyRef = (int) parseUnsignedInt(copyOfRange(bytes,
+ offset + contentOffset + i * objectRefSize,
+ offset + contentOffset + (i + 1) * objectRefSize));
+ int valRef = (int) parseUnsignedInt(copyOfRange(bytes,
+ offset + contentOffset + (length * objectRefSize) + i * objectRefSize,
+ offset + contentOffset + (length * objectRefSize) + (i + 1) * objectRefSize));
+ NSObject key = parseObject(keyRef);
+ NSObject val = parseObject(valRef);
+ dict.put(key.toString(), val);
+ }
+ return dict;
+ }
+ default: {
+ System.err.println("WARNING: The given binary property list contains an object of unknown type (" + objType + ")");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Reads the length for arrays, sets and dictionaries.
+ *
+ * @param objInfo Object information byte.
+ * @param offset Offset in the byte array at which the object is located.
+ * @return An array with the length two. First entry is the length, second entry the offset at which the content starts.
+ */
+ private int[] readLengthAndOffset(int objInfo, int offset) {
+ int length = objInfo;
+ int stroffset = 1;
+ if (objInfo == 0xF) {
+ int int_type = bytes[offset + 1];
+ int intType = (int_type & 0xF0) >> 4;
+ if (intType != 0x1) {
+ System.err.println("BinaryPropertyListParser: Length integer has an unexpected type" + intType + ". Attempting to parse anyway...");
+ }
+ int intInfo = int_type & 0x0F;
+ int intLength = (int) Math.pow(2, intInfo);
+ stroffset = 2 + intLength;
+ if (intLength < 3) {
+ length = (int) parseUnsignedInt(copyOfRange(bytes, offset + 2, offset + 2 + intLength));
+ } else {
+ length = new BigInteger(copyOfRange(bytes, offset + 2, offset + 2 + intLength)).intValue();
+ }
+ }
+ return new int[]{length, stroffset};
+ }
+
+ /**
+ * Parses an unsigned integers from a byte array.
+ *
+ * @param bytes The byte array containing the unsigned integer.
+ * @return The unsigned integer represented by the given bytes.
+ */
+ public static long parseUnsignedInt(byte[] bytes) {
+ long l = 0;
+ for (byte b : bytes) {
+ l <<= 8;
+ l |= b & 0xFF;
+ }
+ l &= 0xFFFFFFFFL;
+ return l;
+ }
+
+ /**
+ * Parses an unsigned integer from a byte array.
+ *
+ * @param bytes The byte array containing the unsigned integer.
+ * @param startIndex Beginning of the unsigned int in the byte array.
+ * @param endIndex End of the unsigned int in the byte array.
+ * @return The unsigned integer represented by the given bytes.
+ */
+ public static long parseUnsignedInt(byte[] bytes, int startIndex, int endIndex) {
+ long l = 0;
+ for (int i = startIndex; i < endIndex; i++) {
+ l <<= 8;
+ l |= bytes[i] & 0xFF;
+ }
+ l &= 0xFFFFFFFFL;
+ return l;
+ }
+
+ /**
+ * Parses a long from a (big-endian) byte array.
+ *
+ * @param bytes The bytes representing the long integer.
+ * @return The long integer represented by the given bytes.
+ */
+ public static long parseLong(byte[] bytes) {
+ long l = 0;
+ for (byte b : bytes) {
+ l <<= 8;
+ l |= b & 0xFF;
+ }
+ return l;
+ }
+
+ /**
+ * Parses a long from a (big-endian) byte array.
+ *
+ * @param bytes The bytes representing the long integer.
+ * @param startIndex Beginning of the long in the byte array.
+ * @param endIndex End of the long in the byte array.
+ * @return The long integer represented by the given bytes.
+ */
+ public static long parseLong(byte[] bytes, int startIndex, int endIndex) {
+ long l = 0;
+ for (int i = startIndex; i < endIndex; i++) {
+ l <<= 8;
+ l |= bytes[i] & 0xFF;
+ }
+ return l;
+ }
+
+ /**
+ * Parses a double from a (big-endian) byte array.
+ *
+ * @param bytes The bytes representing the double.
+ * @return The double represented by the given bytes.
+ */
+ public static double parseDouble(byte[] bytes) {
+ if (bytes.length == 8) {
+ return Double.longBitsToDouble(parseLong(bytes));
+ } else if (bytes.length == 4) {
+ return Float.intBitsToFloat((int) parseLong(bytes));
+ } else {
+ throw new IllegalArgumentException("bad byte array length " + bytes.length);
+ }
+ }
+
+ /**
+ * Parses a double from a (big-endian) byte array.
+ *
+ * @param bytes The bytes representing the double.
+ * @param startIndex Beginning of the double in the byte array.
+ * @param endIndex End of the double in the byte array.
+ * @return The double represented by the given bytes.
+ */
+ public static double parseDouble(byte[] bytes, int startIndex, int endIndex) {
+ if (endIndex - startIndex == 8) {
+ return Double.longBitsToDouble(parseLong(bytes, startIndex, endIndex));
+ } else if (endIndex - startIndex == 4) {
+ return Float.intBitsToFloat((int)parseLong(bytes, startIndex, endIndex));
+ } else {
+ throw new IllegalArgumentException("endIndex ("+endIndex+") - startIndex ("+startIndex+") != 4 or 8");
+ }
+ }
+
+ /**
+ * Copies a part of a byte array into a new array.
+ *
+ * @param src The source array.
+ * @param startIndex The index from which to start copying.
+ * @param endIndex The index until which to copy.
+ * @return The copied array.
+ */
+ public static byte[] copyOfRange(byte[] src, int startIndex, int endIndex) {
+ int length = endIndex - startIndex;
+ if (length < 0) {
+ throw new IllegalArgumentException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + ")");
+ }
+ byte[] dest = new byte[length];
+ System.arraycopy(src, startIndex, dest, 0, length);
+ return dest;
+ }
+}
+
diff --git a/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListWriter.java b/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListWriter.java
new file mode 100644
index 0000000000..1ab0b5de8b
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/BinaryPropertyListWriter.java
@@ -0,0 +1,301 @@
+/*
+ * 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.HashMap;
+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
+ */
+ 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
+ */
+ 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
+ */
+ 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 HashMap<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 If an error occured while writing to the stream
+ */
+ 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));
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSArray.java b/third_party/java/dd_plist/java/com/dd/plist/NSArray.java
new file mode 100644
index 0000000000..b8324c1bff
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSArray.java
@@ -0,0 +1,335 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+ * 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.IOException;
+import java.util.Arrays;
+
+/**
+ * Represents an Array.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class NSArray extends NSObject {
+
+ private NSObject[] array;
+
+ /**
+ * Creates an empty array of the given length.
+ *
+ * @param length The number of elements this array will be able to hold.
+ */
+ public NSArray(int length) {
+ array = new NSObject[length];
+ }
+
+ /**
+ * Creates a array from an existing one
+ *
+ * @param a The array which should be wrapped by the NSArray
+ */
+ public NSArray(NSObject... a) {
+ array = a;
+ }
+
+ /**
+ * Returns the object stored at the given index.
+ * Equivalent to <code>getArray()[i]</code>.
+ *
+ * @param i The index of the object.
+ * @return The object at the given index.
+ */
+ public NSObject objectAtIndex(int i) {
+ return array[i];
+ }
+
+ /**
+ * Remove the i-th element from the array.
+ * The array will be resized.
+ *
+ * @param i The index of the object
+ */
+ public void remove(int i) {
+ if ((i >= array.length) || (i < 0))
+ throw new ArrayIndexOutOfBoundsException("invalid index:" + i + ";the array length is " + array.length);
+ NSObject[] newArray = new NSObject[array.length - 1];
+ System.arraycopy(array, 0, newArray, 0, i);
+ System.arraycopy(array, i + 1, newArray, i, array.length - i - 1);
+ array = newArray;
+ }
+
+ /**
+ * Stores an object at the specified index.
+ * If there was another object stored at that index it will be replaced.
+ * Equivalent to <code>getArray()[key] = value</code>.
+ *
+ * @param key The index where to store the object.
+ * @param value The object.
+ */
+ public void setValue(int key, Object value) {
+ if(value == null)
+ throw new NullPointerException("Cannot add null values to an NSArray!");
+ array[key] = NSObject.wrap(value);
+ }
+
+ /**
+ * Returns the array of NSObjects represented by this NSArray.
+ * Any changes to the values of this array will also affect the NSArray.
+ *
+ * @return The actual array represented by this NSArray.
+ */
+ public NSObject[] getArray() {
+ return array;
+ }
+
+ /**
+ * Returns the size of the array.
+ *
+ * @return The number of elements that this array can store.
+ */
+ public int count() {
+ return array.length;
+ }
+
+ /**
+ * Checks whether an object is present in the array or whether it is equal
+ * to any of the objects in the array.
+ *
+ * @param obj The object to look for.
+ * @return <code>true</code>, when the object could be found. <code>false</code> otherwise.
+ * @see Object#equals(java.lang.Object)
+ */
+ public boolean containsObject(Object obj) {
+ NSObject nso = NSObject.wrap(obj);
+ for (NSObject elem : array) {
+ if (elem.equals(nso)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Searches for an object in the array. If it is found its index will be
+ * returned. This method also returns an index if the object is not the same
+ * as the one stored in the array but has equal contents.
+ *
+ * @param obj The object to look for.
+ * @return The index of the object, if it was found. -1 otherwise.
+ * @see Object#equals(java.lang.Object)
+ * @see #indexOfIdenticalObject(Object)
+ */
+ public int indexOfObject(Object obj) {
+ NSObject nso = NSObject.wrap(obj);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i].equals(nso)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Searches for an object in the array. If it is found its index will be
+ * returned. This method only returns the index of an object that is
+ * <b>identical</b> to the given one. Thus objects that might contain the
+ * same value as the given one will not be considered.
+ *
+ * @param obj The object to look for.
+ * @return The index of the object, if it was found. -1 otherwise.
+ * @see #indexOfObject(Object)
+ */
+ public int indexOfIdenticalObject(Object obj) {
+ NSObject nso = NSObject.wrap(obj);
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == nso) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the last object contained in this array.
+ * Equivalent to <code>getArray()[getArray().length-1]</code>.
+ *
+ * @return The value of the highest index in the array.
+ */
+ public NSObject lastObject() {
+ return array[array.length - 1];
+ }
+
+ /**
+ * Returns a new array containing only the values stored at the given
+ * indices. The values are sorted by their index.
+ *
+ * @param indexes The indices of the objects.
+ * @return The new array containing the objects stored at the given indices.
+ */
+ public NSObject[] objectsAtIndexes(int... indexes) {
+ NSObject[] result = new NSObject[indexes.length];
+ Arrays.sort(indexes);
+ for (int i = 0; i < indexes.length; i++)
+ result[i] = array[indexes[i]];
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj.getClass().equals(NSArray.class)) {
+ return Arrays.equals(((NSArray) obj).getArray(), this.array);
+ } else {
+ NSObject nso = NSObject.wrap(obj);
+ if(nso.getClass().equals(NSArray.class)) {
+ return Arrays.equals(((NSArray) nso).getArray(), this.array);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 89 * hash + Arrays.deepHashCode(this.array);
+ return hash;
+ }
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<array>");
+ xml.append(NSObject.NEWLINE);
+ for (NSObject o : array) {
+ o.toXML(xml, level + 1);
+ xml.append(NSObject.NEWLINE);
+ }
+ indent(xml, level);
+ xml.append("</array>");
+ }
+
+ @Override
+ void assignIDs(BinaryPropertyListWriter out) {
+ super.assignIDs(out);
+ for (NSObject obj : array) {
+ obj.assignIDs(out);
+ }
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ out.writeIntHeader(0xA, array.length);
+ for (NSObject obj : array) {
+ out.writeID(out.getID(obj));
+ }
+ }
+
+ /**
+ * Generates a valid ASCII property list which has this NSArray as its
+ * root object. The generated property list complies with the format as
+ * described in <a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html">
+ * Property List Programming Guide - Old-Style ASCII Property Lists</a>.
+ *
+ * @return ASCII representation of this object.
+ */
+ public String toASCIIPropertyList() {
+ StringBuilder ascii = new StringBuilder();
+ toASCII(ascii, 0);
+ ascii.append(NEWLINE);
+ return ascii.toString();
+ }
+
+ /**
+ * Generates a valid ASCII property list in GnuStep format which has this
+ * NSArray as its root object. The generated property list complies with
+ * the format as described in <a href="http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html">
+ * GnuStep - NSPropertyListSerialization class documentation
+ * </a>
+ *
+ * @return GnuStep ASCII representation of this object.
+ */
+ public String toGnuStepASCIIPropertyList() {
+ StringBuilder ascii = new StringBuilder();
+ toASCIIGnuStep(ascii, 0);
+ ascii.append(NEWLINE);
+ return ascii.toString();
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
+ int indexOfLastNewLine = ascii.lastIndexOf(NEWLINE);
+ for (int i = 0; i < array.length; i++) {
+ Class<?> objClass = array[i].getClass();
+ if ((objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class))
+ && indexOfLastNewLine != ascii.length()) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ array[i].toASCII(ascii, level + 1);
+ } else {
+ if (i != 0)
+ ascii.append(" ");
+ array[i].toASCII(ascii, 0);
+ }
+
+ if (i != array.length - 1)
+ ascii.append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
+
+ if (ascii.length() - indexOfLastNewLine > ASCII_LINE_LENGTH) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ }
+ }
+ ascii.append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
+ int indexOfLastNewLine = ascii.lastIndexOf(NEWLINE);
+ for (int i = 0; i < array.length; i++) {
+ Class<?> objClass = array[i].getClass();
+ if ((objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class))
+ && indexOfLastNewLine != ascii.length()) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ array[i].toASCIIGnuStep(ascii, level + 1);
+ } else {
+ if (i != 0)
+ ascii.append(" ");
+ array[i].toASCIIGnuStep(ascii, 0);
+ }
+
+ if (i != array.length - 1)
+ ascii.append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
+
+ if (ascii.length() - indexOfLastNewLine > ASCII_LINE_LENGTH) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ }
+ }
+ ascii.append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSData.java b/third_party/java/dd_plist/java/com/dd/plist/NSData.java
new file mode 100644
index 0000000000..d156a2f59f
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSData.java
@@ -0,0 +1,182 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt
+ *
+ * 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.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * NSData objects are wrappers for byte buffers.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class NSData extends NSObject {
+
+ private byte[] bytes;
+
+ /**
+ * Creates the NSData object from the binary representation of it.
+ *
+ * @param bytes The raw data contained in the NSData object.
+ */
+ public NSData(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.
+ *
+ * @param base64 The Base64 encoded contents of the NSData object.
+ * @throws IOException When the given string is not a proper Base64 formatted string.
+ */
+ public NSData(String base64) throws IOException {
+ //Remove all white spaces from the string so that it is parsed completely
+ //and not just until the first white space occurs.
+ String data = base64.replaceAll("\\s+", "");
+ bytes = Base64.decode(data);
+ }
+
+ /**
+ * Creates a NSData object from a file. Using the files contents as the contents of this NSData object.
+ *
+ * @param file The file containing the data.
+ * @throws FileNotFoundException If the file could not be found.
+ * @throws IOException If the file could not be read.
+ */
+ public NSData(File file) throws IOException {
+ bytes = new byte[(int) file.length()];
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+ raf.read(bytes);
+ raf.close();
+ }
+
+ /**
+ * The bytes contained in this NSData object.
+ *
+ * @return The data as bytes
+ */
+ public byte[] bytes() {
+ return bytes;
+ }
+
+ /**
+ * Gets the amount of data stored in this object.
+ *
+ * @return The number of bytes contained in this object.
+ */
+ public int length() {
+ return bytes.length;
+ }
+
+ /**
+ * Loads the bytes from this NSData object into a byte buffer
+ *
+ * @param buf The byte buffer which will contain the data
+ * @param length The amount of data to copy
+ */
+ public void getBytes(ByteBuffer buf, int length) {
+ buf.put(bytes, 0, Math.min(bytes.length, length));
+ }
+
+ /**
+ * Loads the bytes from this NSData object into a byte buffer
+ *
+ * @param buf The byte buffer which will contain the data
+ * @param rangeStart The start index
+ * @param rangeStop The stop index
+ */
+ public void getBytes(ByteBuffer buf, int rangeStart, int rangeStop) {
+ buf.put(bytes, rangeStart, Math.min(bytes.length, rangeStop));
+ }
+
+ /**
+ * Gets the Base64 encoded data contained in this NSData object.
+ *
+ * @return The Base64 encoded data as a <code>String</code>.
+ */
+ public String getBase64EncodedData() {
+ return Base64.encodeBytes(bytes);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj.getClass().equals(getClass()) && Arrays.equals(((NSData) obj).bytes, bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 67 * hash + Arrays.hashCode(this.bytes);
+ return hash;
+ }
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<data>");
+ xml.append(NSObject.NEWLINE);
+ String base64 = getBase64EncodedData();
+ for (String line : base64.split("\n")) {
+ indent(xml, level + 1);
+ xml.append(line);
+ xml.append(NSObject.NEWLINE);
+ }
+ indent(xml, level);
+ xml.append("</data>");
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ out.writeIntHeader(0x4, bytes.length);
+ out.write(bytes);
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN);
+ int indexOfLastNewLine = ascii.lastIndexOf(NEWLINE);
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i] & 0xFF;
+ if (b < 16)
+ ascii.append("0");
+ ascii.append(Integer.toHexString(b));
+ if (ascii.length() - indexOfLastNewLine > ASCII_LINE_LENGTH) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ } else if ((i + 1) % 2 == 0 && i != bytes.length - 1) {
+ ascii.append(" ");
+ }
+ }
+ ascii.append(ASCIIPropertyListParser.DATA_END_TOKEN);
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ toASCII(ascii, level);
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSDate.java b/third_party/java/dd_plist/java/com/dd/plist/NSDate.java
new file mode 100644
index 0000000000..5fb11f8f88
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSDate.java
@@ -0,0 +1,185 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt, 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.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Represents a date.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class NSDate extends NSObject {
+
+ private Date date;
+
+ // EPOCH = new SimpleDateFormat("yyyy MM dd zzz").parse("2001 01 01 GMT").getTime();
+ // ...but that's annoying in a static initializer because it can throw exceptions, ick.
+ // So we just hardcode the correct value.
+ private final static long EPOCH = 978307200000L;
+
+ private static final SimpleDateFormat sdfDefault = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ private static final SimpleDateFormat sdfGnuStep = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
+
+ static {
+ sdfDefault.setTimeZone(TimeZone.getTimeZone("GMT"));
+ sdfGnuStep.setTimeZone(TimeZone.getTimeZone("GMT"));
+ }
+
+ /**
+ * Parses the XML date string and creates a Java Date object from it.
+ * This function is synchronized as SimpleDateFormat is not thread-safe.
+ *
+ * @param textRepresentation The date string as found in the XML property list
+ * @return The parsed Date
+ * @throws ParseException If the given string cannot be parsed.
+ * @see SimpleDateFormat#parse(java.lang.String)
+ */
+ private static synchronized Date parseDateString(String textRepresentation) throws ParseException {
+ try {
+ return sdfDefault.parse(textRepresentation);
+ } catch (ParseException ex) {
+ return sdfGnuStep.parse(textRepresentation);
+ }
+ }
+
+ /**
+ * Generates a String representation of a Java Date object. The string
+ * is formatted according to the specification for XML property list dates.
+ *
+ * @param date The date which should be represented.
+ * @return The string representation of the date.
+ */
+ private static synchronized String makeDateString(Date date) {
+ return sdfDefault.format(date);
+ }
+
+ /**
+ * Generates a String representation of a Java Date object. The string
+ * is formatted according to the specification for GnuStep ASCII property
+ * list dates.
+ *
+ * @param date The date which should be represented.
+ * @return The string representation of the date.
+ */
+ private static synchronized String makeDateStringGnuStep(Date date) {
+ return sdfGnuStep.format(date);
+ }
+
+ /**
+ * Creates a date from its binary representation.
+ *
+ * @param bytes The date bytes
+ */
+ public NSDate(byte[] bytes) {
+ //dates are 8 byte big-endian double, seconds since the epoch
+ date = new Date(EPOCH + (long) (1000 * BinaryPropertyListParser.parseDouble(bytes)));
+ }
+
+ /**
+ * Parses a date from its textual representation.
+ * That representation has the following pattern: <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
+ *
+ * @param textRepresentation The textual representation of the date (ISO 8601 format)
+ * @throws ParseException When the date could not be parsed, i.e. it does not match the expected pattern.
+ */
+ public NSDate(String textRepresentation) throws ParseException {
+ date = parseDateString(textRepresentation);
+ }
+
+ /**
+ * Creates a NSDate from a Java Date
+ *
+ * @param d The date
+ */
+ public NSDate(Date d) {
+ if (d == null)
+ throw new IllegalArgumentException("Date cannot be null");
+ date = d;
+ }
+
+ /**
+ * Gets the date.
+ *
+ * @return The date.
+ */
+ public Date getDate() {
+ return date;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj.getClass().equals(getClass()) && date.equals(((NSDate) obj).getDate());
+ }
+
+ @Override
+ public int hashCode() {
+ return date.hashCode();
+ }
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<date>");
+ xml.append(makeDateString(date));
+ xml.append("</date>");
+ }
+
+ @Override
+ public void toBinary(BinaryPropertyListWriter out) throws IOException {
+ out.write(0x33);
+ out.writeDouble((date.getTime() - EPOCH) / 1000.0);
+ }
+
+ /**
+ * Generates a string representation of the date.
+ *
+ * @return A string representation of the date.
+ * @see java.util.Date#toString()
+ */
+ @Override
+ public String toString() {
+ return date.toString();
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append("\"");
+ ascii.append(makeDateString(date));
+ ascii.append("\"");
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append("<*D");
+ ascii.append(makeDateStringGnuStep(date));
+ ascii.append(">");
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSDictionary.java b/third_party/java/dd_plist/java/com/dd/plist/NSDictionary.java
new file mode 100644
index 0000000000..5d432ad1a7
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSDictionary.java
@@ -0,0 +1,539 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+ * 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.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A NSDictionary is a collection of keys and values, essentially a Hashtable.
+ * The keys are simple Strings whereas the values can be any kind of NSObject.
+ * <p/>
+ * You can access the keys through the function <code>allKeys()</code>. Access
+ * to the objects stored for each key is given through the function
+ * <code>objectoForKey(String key)</code>.
+ *
+ * @author Daniel Dreibrodt
+ * @see java.util.Hashtable
+ * @see com.dd.plist.NSObject
+ */
+public class NSDictionary extends NSObject implements Map<String, NSObject> {
+
+ private HashMap<String, NSObject> dict;
+
+ /**
+ * Creates a new empty NSDictionary.
+ */
+ public NSDictionary() {
+ //With a linked HashMap the order of elements in the dictionary is kept.
+ dict = new LinkedHashMap<String, NSObject>();
+ }
+
+ /**
+ * Gets the hashmap which stores the keys and values of this dictionary.
+ * Changes to the hashmap's contents are directly reflected in this
+ * dictionary.
+ *
+ * @return The hashmap which is used by this dictionary to store its contents.
+ */
+ public HashMap<String, NSObject> getHashMap() {
+ return dict;
+ }
+
+ /**
+ * Gets the NSObject stored for the given key.
+ *
+ * @param key The key.
+ * @return The object.
+ */
+ public NSObject objectForKey(String key) {
+ return dict.get(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#size()
+ */
+ public int size() {
+ return dict.size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#isEmpty()
+ */
+ public boolean isEmpty() {
+ return dict.isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object key) {
+ return dict.containsKey(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#containsValue(java.lang.Object)
+ */
+ public boolean containsValue(Object value) {
+ if(value == null)
+ return false;
+ NSObject wrap = NSObject.wrap(value);
+ return dict.containsValue(wrap);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#get(java.lang.Object)
+ */
+ public NSObject get(Object key) {
+ return dict.get(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#putAll(java.util.Map)
+ */
+ public void putAll(Map<? extends String, ? extends NSObject> values) {
+ for (Object object : values.entrySet()) {
+ @SuppressWarnings("unchecked")
+ Map.Entry<String, NSObject> entry = (Map.Entry<String, NSObject>) object;
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Puts a new key-value pair into this dictionary.
+ * If the value is null, no operation will be performed on the dictionary.
+ *
+ * @param key The key.
+ * @param obj The value.
+ * @return The value previously associated to the given key,
+ * or null, if no value was associated to it.
+ */
+ public NSObject put(String key, NSObject obj) {
+ if(obj == null)
+ return dict.get(key);
+ return dict.put(key, obj);
+ }
+
+ /**
+ * Puts a new key-value pair into this dictionary.
+ * If the value is null, no operation will be performed on the dictionary.
+ *
+ * @param key The key.
+ * @param obj The value. Supported object types are numbers, byte-arrays, dates, strings and arrays or sets of those.
+ * @return The value previously associated to the given key,
+ * or null, if no value was associated to it.
+ */
+ public NSObject put(String key, Object obj) {
+ if(obj == null)
+ return dict.get(key);
+ return put(key, NSObject.wrap(obj));
+ }
+
+ /**
+ * Puts a new key-value pair into this dictionary.
+ *
+ * @param key The key.
+ * @param obj The value.
+ */
+ public NSObject put(String key, long obj) {
+ return put(key, new NSNumber(obj));
+ }
+
+ /**
+ * Puts a new key-value pair into this dictionary.
+ *
+ * @param key The key.
+ * @param obj The value.
+ */
+ public NSObject put(String key, double obj) {
+ return put(key, new NSNumber(obj));
+ }
+
+ /**
+ * Puts a new key-value pair into this dictionary.
+ *
+ * @param key The key.
+ * @param obj The value.
+ */
+ public NSObject put(String key, boolean obj) {
+ return put(key, new NSNumber(obj));
+ }
+
+ /**
+ * Removes a key-value pair from this dictionary.
+ *
+ * @param key The key
+ * @return the value previously associated to the given key.
+ */
+ public NSObject remove(String key) {
+ return dict.remove(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#remove(java.lang.Object)
+ */
+ public NSObject remove(Object key) {
+ return dict.remove(key);
+ }
+
+ /**
+ * Removes all key-value pairs from this dictionary.
+ * @see java.util.Map#clear()
+ */
+ public void clear() {
+ dict.clear();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#keySet()
+ */
+ public Set<String> keySet() {
+ return dict.keySet();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#values()
+ */
+ public Collection<NSObject> values() {
+ return dict.values();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Map#entrySet()
+ */
+ public Set<Entry<String, NSObject>> entrySet() {
+ return dict.entrySet();
+ }
+
+ /**
+ * Checks whether a given key is contained in this dictionary.
+ *
+ * @param key The key that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsKey(String key) {
+ return dict.containsKey(key);
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(NSObject val) {
+ return val != null && dict.containsValue(val);
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(String val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSString.class)) {
+ NSString str = (NSString) o;
+ if (str.getContent().equals(val))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(long val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSNumber.class)) {
+ NSNumber num = (NSNumber) o;
+ if (num.isInteger() && num.intValue() == val)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(double val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSNumber.class)) {
+ NSNumber num = (NSNumber) o;
+ if (num.isReal() && num.doubleValue() == val)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(boolean val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSNumber.class)) {
+ NSNumber num = (NSNumber) o;
+ if (num.isBoolean() && num.boolValue() == val)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(Date val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSDate.class)) {
+ NSDate dat = (NSDate) o;
+ if (dat.getDate().equals(val))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a given value is contained in this dictionary.
+ *
+ * @param val The value that will be searched for.
+ * @return Whether the key is contained in this dictionary.
+ */
+ public boolean containsValue(byte[] val) {
+ for (NSObject o : dict.values()) {
+ if (o.getClass().equals(NSData.class)) {
+ NSData dat = (NSData) o;
+ if (Arrays.equals(dat.bytes(), val))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Counts the number of contained key-value pairs.
+ *
+ * @return The size of this NSDictionary.
+ */
+ public int count() {
+ return dict.size();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj.getClass().equals(this.getClass()) && ((NSDictionary) obj).dict.equals(dict));
+ }
+
+ /**
+ * Gets a list of all keys used in this NSDictionary.
+ *
+ * @return The list of all keys used in this NSDictionary.
+ */
+ public String[] allKeys() {
+ return dict.keySet().toArray(new String[count()]);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + (this.dict != null ? this.dict.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<dict>");
+ xml.append(NSObject.NEWLINE);
+ for (String key : dict.keySet()) {
+ NSObject val = objectForKey(key);
+ indent(xml, level + 1);
+ xml.append("<key>");
+ //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
+ //contain the characters < or &. Also the > character should be escaped.
+ if (key.contains("&") || key.contains("<") || key.contains(">")) {
+ xml.append("<![CDATA[");
+ xml.append(key.replaceAll("]]>", "]]]]><![CDATA[>"));
+ xml.append("]]>");
+ } else {
+ xml.append(key);
+ }
+ xml.append("</key>");
+ xml.append(NSObject.NEWLINE);
+ val.toXML(xml, level + 1);
+ xml.append(NSObject.NEWLINE);
+ }
+ indent(xml, level);
+ xml.append("</dict>");
+ }
+
+ @Override
+ void assignIDs(BinaryPropertyListWriter out) {
+ super.assignIDs(out);
+ for (Map.Entry<String, NSObject> entry : dict.entrySet()) {
+ new NSString(entry.getKey()).assignIDs(out);
+ entry.getValue().assignIDs(out);
+ }
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ out.writeIntHeader(0xD, dict.size());
+ Set<Map.Entry<String, NSObject>> entries = dict.entrySet();
+ for (Map.Entry<String, NSObject> entry : entries) {
+ out.writeID(out.getID(new NSString(entry.getKey())));
+ }
+ for (Map.Entry<String, NSObject> entry : entries) {
+ out.writeID(out.getID(entry.getValue()));
+ }
+ }
+
+ /**
+ * Generates a valid ASCII property list which has this NSDictionary as its
+ * root object. The generated property list complies with the format as
+ * described in <a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html">
+ * Property List Programming Guide - Old-Style ASCII Property Lists</a>.
+ *
+ * @return ASCII representation of this object.
+ */
+ public String toASCIIPropertyList() {
+ StringBuilder ascii = new StringBuilder();
+ toASCII(ascii, 0);
+ ascii.append(NEWLINE);
+ return ascii.toString();
+ }
+
+ /**
+ * Generates a valid ASCII property list in GnuStep format which has this
+ * NSDictionary as its root object. The generated property list complies with
+ * the format as described in <a href="http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html">
+ * GnuStep - NSPropertyListSerialization class documentation
+ * </a>
+ *
+ * @return GnuStep ASCII representation of this object.
+ */
+ public String toGnuStepASCIIPropertyList() {
+ StringBuilder ascii = new StringBuilder();
+ toASCIIGnuStep(ascii, 0);
+ ascii.append(NEWLINE);
+ return ascii.toString();
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
+ ascii.append(NEWLINE);
+ String[] keys = allKeys();
+ for (String key : keys) {
+ NSObject val = objectForKey(key);
+ indent(ascii, level + 1);
+ ascii.append("\"");
+ ascii.append(NSString.escapeStringForASCII(key));
+ ascii.append("\" =");
+ Class<?> objClass = val.getClass();
+ if (objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class)) {
+ ascii.append(NEWLINE);
+ val.toASCII(ascii, level + 2);
+ } else {
+ ascii.append(" ");
+ val.toASCII(ascii, 0);
+ }
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN);
+ ascii.append(NEWLINE);
+ }
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN);
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
+ ascii.append(NEWLINE);
+ String[] keys = dict.keySet().toArray(new String[dict.size()]);
+ for (String key : keys) {
+ NSObject val = objectForKey(key);
+ indent(ascii, level + 1);
+ ascii.append("\"");
+ ascii.append(NSString.escapeStringForASCII(key));
+ ascii.append("\" =");
+ Class<?> objClass = val.getClass();
+ if (objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class)) {
+ ascii.append(NEWLINE);
+ val.toASCIIGnuStep(ascii, level + 2);
+ } else {
+ ascii.append(" ");
+ val.toASCIIGnuStep(ascii, 0);
+ }
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN);
+ ascii.append(NEWLINE);
+ }
+ indent(ascii, level);
+ ascii.append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN);
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSNumber.java b/third_party/java/dd_plist/java/com/dd/plist/NSNumber.java
new file mode 100644
index 0000000000..9baf6f5dec
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSNumber.java
@@ -0,0 +1,407 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt, 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.IOException;
+
+/**
+ * A number whose value is either an integer, a real number or boolean.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class NSNumber extends NSObject implements Comparable<Object> {
+
+ /**
+ * Indicates that the number's value is an integer.
+ * The number is stored as a Java <code>long</code>.
+ * Its original value could have been char, short, int, long or even long long.
+ */
+ public static final int INTEGER = 0;
+
+ /**
+ * Indicates that the number's value is a real number.
+ * The number is stored as a Java <code>double</code>.
+ * Its original value could have been float or double.
+ */
+ public static final int REAL = 1;
+
+ /**
+ * Indicates that the number's value is boolean.
+ */
+ public static final int BOOLEAN = 2;
+
+ //Holds the current type of this number
+ private int type;
+
+ private long longValue;
+ private double doubleValue;
+ private boolean boolValue;
+
+ /**
+ * Parses integers and real numbers from their binary representation.
+ * <i>Note: real numbers are not yet supported.</i>
+ *
+ * @param bytes The binary representation
+ * @param type The type of number
+ * @see #INTEGER
+ * @see #REAL
+ */
+ public NSNumber(byte[] bytes, int type) {
+ switch (type) {
+ case INTEGER: {
+ doubleValue = longValue = BinaryPropertyListParser.parseLong(bytes);
+ break;
+ }
+ case REAL: {
+ doubleValue = BinaryPropertyListParser.parseDouble(bytes);
+ longValue = Math.round(doubleValue);
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Type argument is not valid.");
+ }
+ }
+ this.type = type;
+ }
+
+ /**
+ * Creates a number from its textual representation.
+ *
+ * @param text The textual representation of the number.
+ * @throws IllegalArgumentException If the text does not represent an integer, real number or boolean value.
+ * @see Boolean#parseBoolean(java.lang.String)
+ * @see Long#parseLong(java.lang.String)
+ * @see Double#parseDouble(java.lang.String)
+ */
+ public NSNumber(String text) {
+ if (text == null)
+ throw new IllegalArgumentException("The given string is null and cannot be parsed as number.");
+ try {
+ long l = Long.parseLong(text);
+ doubleValue = longValue = l;
+ type = INTEGER;
+ } catch (Exception ex) {
+ try {
+ doubleValue = Double.parseDouble(text);
+ longValue = Math.round(doubleValue);
+ type = REAL;
+ } catch (Exception ex2) {
+ try {
+ boolValue = text.toLowerCase().equals("true") || text.toLowerCase().equals("yes");
+ if(!boolValue && !(text.toLowerCase().equals("false") || text.toLowerCase().equals("no"))) {
+ throw new Exception("not a boolean");
+ }
+ type = BOOLEAN;
+ doubleValue = longValue = boolValue ? 1 : 0;
+ } catch (Exception ex3) {
+ throw new IllegalArgumentException("The given string neither represents a double, an int nor a boolean value.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates an integer number.
+ *
+ * @param i The integer value.
+ */
+ public NSNumber(int i) {
+ doubleValue = longValue = i;
+ type = INTEGER;
+ }
+
+ /**
+ * Creates an integer number.
+ *
+ * @param l The long integer value.
+ */
+ public NSNumber(long l) {
+ doubleValue = longValue = l;
+ type = INTEGER;
+ }
+
+ /**
+ * Creates a real number.
+ *
+ * @param d The real value.
+ */
+ public NSNumber(double d) {
+ longValue = (long) (doubleValue = d);
+ type = REAL;
+ }
+
+ /**
+ * Creates a boolean number.
+ *
+ * @param b The boolean value.
+ */
+ public NSNumber(boolean b) {
+ boolValue = b;
+ doubleValue = longValue = b ? 1 : 0;
+ type = BOOLEAN;
+ }
+
+ /**
+ * Gets the type of this number's value.
+ *
+ * @return The type flag.
+ * @see #BOOLEAN
+ * @see #INTEGER
+ * @see #REAL
+ */
+ public int type() {
+ return type;
+ }
+
+ /**
+ * Checks whether the value of this NSNumber is a boolean.
+ *
+ * @return Whether the number's value is a boolean.
+ */
+ public boolean isBoolean() {
+ return type == BOOLEAN;
+ }
+
+ /**
+ * Checks whether the value of this NSNumber is an integer.
+ *
+ * @return Whether the number's value is an integer.
+ */
+ public boolean isInteger() {
+ return type == INTEGER;
+ }
+
+ /**
+ * Checks whether the value of this NSNumber is a real number.
+ *
+ * @return Whether the number's value is a real number.
+ */
+ public boolean isReal() {
+ return type == REAL;
+ }
+
+ /**
+ * The number's boolean value.
+ *
+ * @return <code>true</code> if the value is true or non-zero, false</code> otherwise.
+ */
+ public boolean boolValue() {
+ if (type == BOOLEAN)
+ return boolValue;
+ else
+ return longValue != 0;
+ }
+
+ /**
+ * The number's long value.
+ *
+ * @return The value of the number as long
+ */
+ public long longValue() {
+ return longValue;
+ }
+
+ /**
+ * The number's int value.
+ * <i>Note: Even though the number's type might be INTEGER it can be larger than a Java int.
+ * Use intValue() only if you are certain that it contains a number from the int range.
+ * Otherwise the value might be innaccurate.</i>
+ *
+ * @return The value of the number as int
+ */
+ public int intValue() {
+ return (int) longValue;
+ }
+
+ /**
+ * The number's double value.
+ *
+ * @return The value of the number as double.
+ */
+ public double doubleValue() {
+ return doubleValue;
+ }
+
+ /**
+ * The number's float value.
+ * WARNING: Possible loss of precision if the value is outside the float range.
+ *
+ * @return The value of the number as float.
+ */
+ public float floatValue() {
+ return (float) doubleValue;
+ }
+
+ /**
+ * Checks whether the other object is a NSNumber of the same value.
+ *
+ * @param obj The object to compare to.
+ * @return Whether the objects are equal in terms of numeric value and type.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof NSNumber)) return false;
+ NSNumber n = (NSNumber) obj;
+ return type == n.type && longValue == n.longValue && doubleValue == n.doubleValue && boolValue == n.boolValue;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = type;
+ hash = 37 * hash + (int) (this.longValue ^ (this.longValue >>> 32));
+ hash = 37 * hash + (int) (Double.doubleToLongBits(this.doubleValue) ^ (Double.doubleToLongBits(this.doubleValue) >>> 32));
+ hash = 37 * hash + (boolValue() ? 1 : 0);
+ return hash;
+ }
+
+
+ @Override
+ public String toString() {
+ switch (type) {
+ case INTEGER: {
+ return String.valueOf(longValue());
+ }
+ case REAL: {
+ return String.valueOf(doubleValue());
+ }
+ case BOOLEAN: {
+ return String.valueOf(boolValue());
+ }
+ default: {
+ return super.toString();
+ }
+ }
+ }
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ switch (type) {
+ case INTEGER: {
+ xml.append("<integer>");
+ xml.append(longValue());
+ xml.append("</integer>");
+ break;
+ }
+ case REAL: {
+ xml.append("<real>");
+ xml.append(doubleValue());
+ xml.append("</real>");
+ break;
+ }
+ case BOOLEAN: {
+ if (boolValue())
+ xml.append("<true/>");
+ else
+ xml.append("<false/>");
+ break;
+ }
+ }
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ switch (type()) {
+ case INTEGER: {
+ if (longValue() < 0) {
+ out.write(0x13);
+ out.writeBytes(longValue(), 8);
+ } else if (longValue() <= 0xff) {
+ out.write(0x10);
+ out.writeBytes(longValue(), 1);
+ } else if (longValue() <= 0xffff) {
+ out.write(0x11);
+ out.writeBytes(longValue(), 2);
+ } else if (longValue() <= 0xffffffffL) {
+ out.write(0x12);
+ out.writeBytes(longValue(), 4);
+ } else {
+ out.write(0x13);
+ out.writeBytes(longValue(), 8);
+ }
+ break;
+ }
+ case REAL: {
+ out.write(0x23);
+ out.writeDouble(doubleValue());
+ break;
+ }
+ case BOOLEAN: {
+ out.write(boolValue() ? 0x09 : 0x08);
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ if (type == BOOLEAN) {
+ ascii.append(boolValue ? "YES" : "NO");
+ } else {
+ ascii.append(toString());
+ }
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ switch (type) {
+ case INTEGER: {
+ ascii.append("<*I");
+ ascii.append(toString());
+ ascii.append(">");
+ break;
+ }
+ case REAL: {
+ ascii.append("<*R");
+ ascii.append(toString());
+ ascii.append(">");
+ break;
+ }
+ case BOOLEAN: {
+ if (boolValue) {
+ ascii.append("<*BY>");
+ } else {
+ ascii.append("<*BN>");
+ }
+ }
+ }
+ }
+
+ public int compareTo(Object o) {
+ double x = doubleValue();
+ double y;
+ if (o instanceof NSNumber) {
+ NSNumber num = (NSNumber) o;
+ y = num.doubleValue();
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);
+ } else if (o instanceof Number) {
+ y = ((Number) o).doubleValue();
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSObject.java b/third_party/java/dd_plist/java/com/dd/plist/NSObject.java
new file mode 100644
index 0000000000..6618fa13b7
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSObject.java
@@ -0,0 +1,431 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+* 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.*;
+
+/**
+ * Abstract interface for any object contained in a property list.
+ * The names and functions of the various objects orient themselves
+ * towards Apple's Cocoa API.
+ *
+ * @author Daniel Dreibrodt
+ */
+public abstract class NSObject {
+
+ /**
+ * The newline character used for generating the XML output.
+ * This constant will be different depending on the operating system on
+ * which you use this library.
+ */
+ final static String NEWLINE = System.getProperty("line.separator");
+
+ /**
+ * The identation character used for generating the XML output. This is the
+ * tabulator character.
+ */
+ final static String INDENT = "\t";
+
+ /**
+ * The maximum length of the text lines to be used when generating
+ * ASCII property lists. But this number is only a guideline it is not
+ * guaranteed that it will not be overstepped.
+ */
+ final static int ASCII_LINE_LENGTH = 80;
+
+ /**
+ * Generates the XML representation of the object (without XML headers or enclosing plist-tags).
+ *
+ * @param xml The StringBuilder onto which the XML representation is appended.
+ * @param level The indentation level of the object.
+ */
+ abstract void toXML(StringBuilder xml, int level);
+
+ /**
+ * Assigns IDs to all the objects in this NSObject subtree.
+ *
+ * @param out The writer object that handles the binary serialization.
+ */
+ void assignIDs(BinaryPropertyListWriter out) {
+ out.assignID(this);
+ }
+
+ /**
+ * Generates the binary representation of the object.
+ *
+ * @param out The output stream to serialize the object to.
+ */
+ abstract void toBinary(BinaryPropertyListWriter out) throws IOException;
+
+ /**
+ * Generates a valid XML property list including headers using this object as root.
+ *
+ * @return The XML representation of the property list including XML header and doctype information.
+ */
+ public String toXMLPropertyList() {
+ StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ xml.append(NSObject.NEWLINE);
+ xml.append("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
+ xml.append(NSObject.NEWLINE);
+ xml.append("<plist version=\"1.0\">");
+ xml.append(NSObject.NEWLINE);
+ toXML(xml, 0);
+ xml.append(NSObject.NEWLINE);
+ xml.append("</plist>");
+ return xml.toString();
+ }
+
+ /**
+ * Generates the ASCII representation of this object.
+ * The generated ASCII representation does not end with a newline.
+ * Complies with https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
+ *
+ * @param ascii The StringBuilder onto which the ASCII representation is appended.
+ * @param level The indentation level of the object.
+ */
+ protected abstract void toASCII(StringBuilder ascii, int level);
+
+ /**
+ * Generates the ASCII representation of this object in the GnuStep format.
+ * The generated ASCII representation does not end with a newline.
+ *
+ * @param ascii The StringBuilder onto which the ASCII representation is appended.
+ * @param level The indentation level of the object.
+ */
+ protected abstract void toASCIIGnuStep(StringBuilder ascii, int level);
+
+ /**
+ * Helper method that adds correct identation to the xml output.
+ * Calling this method will add <code>level</code> number of tab characters
+ * to the <code>xml</code> string.
+ *
+ * @param xml The string builder for the XML document.
+ * @param level The level of identation.
+ */
+ void indent(StringBuilder xml, int level) {
+ for (int i = 0; i < level; i++)
+ xml.append(INDENT);
+ }
+
+ /**
+ * Wraps the given value inside a NSObject.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ */
+ public static NSNumber wrap(long value) {
+ return new NSNumber(value);
+ }
+
+ /**
+ * Wraps the given value inside a NSObject.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ */
+ public static NSNumber wrap(double value) {
+ return new NSNumber(value);
+ }
+
+ /**
+ * Wraps the given value inside a NSObject.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ */
+ public static NSNumber wrap(boolean value) {
+ return new NSNumber(value);
+ }
+
+ /**
+ * Wraps the given value inside a NSObject.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ */
+ public static NSData wrap(byte[] value) {
+ return new NSData(value);
+ }
+
+ /**
+ * Creates a NSArray with the contents of the given array.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ * @throws RuntimeException When one of the objects contained in the array cannot be represented by a NSObject.
+ */
+ public static NSArray wrap(Object[] value) {
+ NSArray arr = new NSArray(value.length);
+ for (int i = 0; i < value.length; i++) {
+ arr.setValue(i, wrap(value[i]));
+ }
+ return arr;
+ }
+
+ /**
+ * Creates a NSDictionary with the contents of the given map.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ * @throws RuntimeException When one of the values contained in the map cannot be represented by a NSObject.
+ */
+ public static NSDictionary wrap(Map<String, Object> value) {
+ NSDictionary dict = new NSDictionary();
+ for (String key : value.keySet())
+ dict.put(key, wrap(value.get(key)));
+ return dict;
+ }
+
+ /**
+ * Creates a NSSet with the contents of this set.
+ *
+ * @param value The value to represent as a NSObject.
+ * @return A NSObject representing the given value.
+ * @throws RuntimeException When one of the values contained in the set cannot be represented by a NSObject.
+ */
+ public static NSSet wrap(Set<Object> value) {
+ NSSet set = new NSSet();
+ for (Object o : value.toArray())
+ set.addObject(wrap(o));
+ return set;
+ }
+
+ /**
+ * Creates a NSObject representing the given Java Object.
+ *
+ * Numerics of type bool, int, long, short, byte, float or double are wrapped as NSNumber objects.
+ *
+ * Strings are wrapped as NSString objects abd byte arrays as NSData objects.
+ *
+ * Date objects are wrapped as NSDate objects.
+ *
+ * Serializable classes are serialized and their data is stored in NSData objects.
+ *
+ * Arrays and Collection objects are converted to NSArrays where each array member is wrapped into a NSObject.
+ *
+ * Map objects are converted to NSDictionaries. Each key is converted to a string and each value wrapped into a NSObject.
+ *
+ * @param o The object to represent.
+ * @return A NSObject equivalent to the given object.
+ */
+ public static NSObject wrap(Object o) {
+ if(o == null)
+ throw new NullPointerException("A null object cannot be wrapped as a NSObject");
+
+ if(o instanceof NSObject)
+ return (NSObject)o;
+
+ Class<?> c = o.getClass();
+ if (Boolean.class.equals(c)) {
+ return wrap((boolean) (Boolean) o);
+ }
+ if (Byte.class.equals(c)) {
+ return wrap((int) (Byte) o);
+ }
+ if (Short.class.equals(c)) {
+ return wrap((int) (Short) o);
+ }
+ if (Integer.class.equals(c)) {
+ return wrap((int) (Integer) o);
+ }
+ if (Long.class.isAssignableFrom(c)) {
+ return wrap((long) (Long) o);
+ }
+ if (Float.class.equals(c)) {
+ return wrap((double) (Float) o);
+ }
+ if (Double.class.isAssignableFrom(c)) {
+ return wrap((double) (Double) o);
+ }
+ if (String.class.equals(c)) {
+ return new NSString((String)o);
+ }
+ if (Date.class.equals(c)) {
+ return new NSDate((Date)o);
+ }
+ if(c.isArray()) {
+ Class<?> cc = c.getComponentType();
+ if (cc.equals(byte.class)) {
+ return wrap((byte[]) o);
+ }
+ else if(cc.equals(boolean.class)) {
+ boolean[] array = (boolean[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else if(float.class.equals(cc)) {
+ float[] array = (float[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else if(double.class.equals(cc)) {
+ double[] array = (double[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else if(short.class.equals(cc)) {
+ short[] array = (short[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else if(int.class.equals(cc)) {
+ int[] array = (int[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else if(long.class.equals(cc)) {
+ long[] array = (long[])o;
+ NSArray nsa = new NSArray(array.length);
+ for(int i=0;i<array.length;i++)
+ nsa.setValue(i, wrap(array[i]));
+ return nsa;
+ }
+ else {
+ return wrap((Object[]) o);
+ }
+ }
+ if (Map.class.isAssignableFrom(c)) {
+ Map map = (Map)o;
+ Set keys = map.keySet();
+ NSDictionary dict = new NSDictionary();
+ for(Object key:keys) {
+ Object val = map.get(key);
+ dict.put(String.valueOf(key), wrap(val));
+ }
+ return dict;
+ }
+ if (Collection.class.isAssignableFrom(c)) {
+ Collection coll = (Collection)o;
+ return wrap(coll.toArray());
+ }
+ return wrapSerialized(o);
+ }
+
+ /**
+ * Serializes the given object using Java's default object serialization
+ * and wraps the serialized object in a NSData object.
+ *
+ * @param o The object to serialize and wrap.
+ * @return A NSData object
+ * @throws RuntimeException When the object could not be serialized.
+ */
+ public static NSData wrapSerialized(Object o) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(o);
+ return new NSData(baos.toByteArray());
+ } catch (IOException ex) {
+ throw new RuntimeException("The given object of class " + o.getClass().toString() + " could not be serialized and stored in a NSData object.");
+ }
+ }
+
+ /**
+ * Converts this NSObject into an equivalent object
+ * of the Java Runtime Environment.
+ * <ul>
+ * <li>NSArray objects are converted to arrays.</li>
+ * <li>NSDictionary objects are converted to objects extending the java.util.Map class.</li>
+ * <li>NSSet objects are converted to objects extending the java.util.Set class.</li>
+ * <li>NSNumber objects are converted to primitive number values (int, long, double or boolean).</li>
+ * <li>NSString objects are converted to String objects.</li>
+ * <li>NSData objects are converted to byte arrays.</li>
+ * <li>NSDate objects are converted to java.util.Date objects.</li>
+ * <li>UID objects are converted to byte arrays.</li>
+ * </ul>
+ * @return A native java object representing this NSObject's value.
+ */
+ public Object toJavaObject() {
+ if(this instanceof NSArray) {
+ NSObject[] arrayA = ((NSArray)this).getArray();
+ Object[] arrayB = new Object[arrayA.length];
+ for(int i = 0; i < arrayA.length; i++) {
+ arrayB[i] = arrayA[i].toJavaObject();
+ }
+ return arrayB;
+ } else if (this instanceof NSDictionary) {
+ HashMap<String, NSObject> hashMapA = ((NSDictionary)this).getHashMap();
+ HashMap<String, Object> hashMapB = new HashMap<String, Object>(hashMapA.size());
+ for(String key:hashMapA.keySet()) {
+ hashMapB.put(key, hashMapA.get(key).toJavaObject());
+ }
+ return hashMapB;
+ } else if(this instanceof NSSet) {
+ Set<NSObject> setA = ((NSSet)this).getSet();
+ Set<Object> setB;
+ if(setA instanceof LinkedHashSet) {
+ setB = new LinkedHashSet<Object>(setA.size());
+ } else {
+ setB = new TreeSet<Object>();
+ }
+ for(NSObject o:setA) {
+ setB.add(o.toJavaObject());
+ }
+ return setB;
+ } else if(this instanceof NSNumber) {
+ NSNumber num = (NSNumber)this;
+ switch(num.type()) {
+ case NSNumber.INTEGER : {
+ long longVal = num.longValue();
+ if(longVal > Integer.MAX_VALUE || longVal < Integer.MIN_VALUE) {
+ return longVal;
+ } else {
+ return num.intValue();
+ }
+ }
+ case NSNumber.REAL : {
+ return num.doubleValue();
+ }
+ case NSNumber.BOOLEAN : {
+ return num.boolValue();
+ }
+ default : {
+ return num.doubleValue();
+ }
+ }
+ } else if(this instanceof NSString) {
+ return ((NSString)this).getContent();
+ } else if(this instanceof NSData) {
+ return ((NSData)this).bytes();
+ } else if(this instanceof NSDate) {
+ return ((NSDate)this).getDate();
+ } else if(this instanceof UID) {
+ return ((UID)this).getBytes();
+ } else {
+ return this;
+ }
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSSet.java b/third_party/java/dd_plist/java/com/dd/plist/NSSet.java
new file mode 100644
index 0000000000..2528951978
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSSet.java
@@ -0,0 +1,354 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt
+ *
+ * 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.IOException;
+import java.util.*;
+
+/**
+ * A set is an interface to an unordered collection of objects.
+ * This implementation uses a <code>LinkedHashSet</code> or <code>TreeSet</code>as the underlying
+ * data structure.
+ * <p/>
+ * <b>Warning:</b> Sets cannot yet be used for saving in binary property lists, as binary property list format v1+ is required to save them.
+ *
+ * @author Daniel Dreibrodt
+ * @see LinkedHashSet
+ */
+public class NSSet extends NSObject {
+
+ private Set<NSObject> set;
+
+ private boolean ordered = false;
+
+ /**
+ * Creates an empty unordered set.
+ *
+ * @see java.util.LinkedHashSet
+ */
+ public NSSet() {
+ set = new LinkedHashSet<NSObject>();
+ }
+
+ /**
+ * Creates an empty set.
+ *
+ * @param ordered Indicates whether the created set should be ordered or unordered.
+ * @see java.util.LinkedHashSet
+ * @see java.util.TreeSet
+ */
+ public NSSet(boolean ordered) {
+ this.ordered = ordered;
+ if (!ordered)
+ set = new LinkedHashSet<NSObject>();
+ else
+ set = new TreeSet<NSObject>();
+ }
+
+ /**
+ * Create a set and fill it with the given objects.
+ *
+ * @param objects The objects to populate the set.
+ * @see java.util.LinkedHashSet
+ */
+ public NSSet(NSObject... objects) {
+ set = new LinkedHashSet<NSObject>();
+ set.addAll(Arrays.asList(objects));
+ }
+
+ /**
+ * Create a set and fill it with the given objects.
+ *
+ * @param objects The objects to populate the set.
+ * @see java.util.LinkedHashSet
+ * @see java.util.TreeSet
+ */
+ public NSSet(boolean ordered, NSObject... objects) {
+ this.ordered = ordered;
+ if (!ordered)
+ set = new LinkedHashSet<NSObject>();
+ else
+ set = new TreeSet<NSObject>();
+ set.addAll(Arrays.asList(objects));
+ }
+
+ /**
+ * Adds an object to the set.
+ *
+ * @param obj The object to add.
+ */
+ public synchronized void addObject(NSObject obj) {
+ set.add(obj);
+ }
+
+ /**
+ * Removes an object from the set.
+ *
+ * @param obj The object to remove.
+ */
+ public synchronized void removeObject(NSObject obj) {
+ set.remove(obj);
+ }
+
+ /**
+ * Returns all objects contained in the set.
+ *
+ * @return An array of all objects in the set.
+ */
+ public synchronized NSObject[] allObjects() {
+ return set.toArray(new NSObject[count()]);
+ }
+
+ /**
+ * Returns one of the objects in the set, or <code>null</code>
+ * if the set contains no objects.
+ *
+ * @return The first object in the set, or <code>null</code> if the set is empty.
+ */
+ public synchronized NSObject anyObject() {
+ if (set.isEmpty())
+ return null;
+ else
+ return set.iterator().next();
+ }
+
+ /**
+ * Finds out whether a given object is contained in the set.
+ *
+ * @param obj The object to look for.
+ * @return <code>true</code>, when the object was found, <code>false</code> otherwise.
+ */
+ public boolean containsObject(NSObject obj) {
+ return set.contains(obj);
+ }
+
+ /**
+ * Determines whether the set contains an object equal to a given object
+ * and returns that object if it is present.
+ *
+ * @param obj The object to look for.
+ * @return The object if it is present, <code>null</code> otherwise.
+ */
+ public synchronized NSObject member(NSObject obj) {
+ for (NSObject o : set) {
+ if (o.equals(obj))
+ return o;
+ }
+ return null;
+ }
+
+ /**
+ * Finds out whether at least one object is present in both sets.
+ *
+ * @param otherSet The other set.
+ * @return <code>false</code> if the intersection of both sets is empty, <code>true</code> otherwise.
+ */
+ public synchronized boolean intersectsSet(NSSet otherSet) {
+ for (NSObject o : set) {
+ if (otherSet.containsObject(o))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Finds out if this set is a subset of the given set.
+ *
+ * @param otherSet The other set.
+ * @return <code>true</code> if all elements in this set are also present in the other set, <code>false</code> otherwise.
+ */
+ public synchronized boolean isSubsetOfSet(NSSet otherSet) {
+ for (NSObject o : set) {
+ if (!otherSet.containsObject(o))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns an iterator object that lets you iterate over all elements of the set.
+ * This is the equivalent to <code>objectEnumerator</code> in the Cocoa implementation
+ * of NSSet.
+ *
+ * @return The iterator for the set.
+ */
+ public synchronized Iterator<NSObject> objectIterator() {
+ return set.iterator();
+ }
+
+ /**
+ * Gets the underlying data structure in which this NSSets stores its content.
+ * @return A Set object.
+ */
+ Set<NSObject> getSet() {
+ return set;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 29 * hash + (this.set != null ? this.set.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final NSSet other = (NSSet) obj;
+ return !(this.set != other.set && (this.set == null || !this.set.equals(other.set)));
+ }
+
+ /**
+ * Gets the number of elements in the set.
+ *
+ * @return The number of elements in the set.
+ * @see Set#size()
+ */
+ public synchronized int count() {
+ return set.size();
+ }
+
+ /**
+ * Returns the XML representantion for this set.
+ * There is no official XML representation specified for sets.
+ * In this implementation it is represented by an array.
+ *
+ * @param xml The XML StringBuilder
+ * @param level The indentation level
+ */
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<array>");
+ xml.append(NSObject.NEWLINE);
+ for (NSObject o : set) {
+ o.toXML(xml, level + 1);
+ xml.append(NSObject.NEWLINE);
+ }
+ indent(xml, level);
+ xml.append("</array>");
+ }
+
+ @Override
+ void assignIDs(BinaryPropertyListWriter out) {
+ super.assignIDs(out);
+ for (NSObject obj : set) {
+ obj.assignIDs(out);
+ }
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ if (ordered) {
+ out.writeIntHeader(0xB, set.size());
+ } else {
+ out.writeIntHeader(0xC, set.size());
+ }
+ for (NSObject obj : set) {
+ out.writeID(out.getID(obj));
+ }
+ }
+
+ /**
+ * Returns the ASCII representation of this set.
+ * There is no official ASCII representation for sets.
+ * In this implementation sets are represented as arrays.
+ *
+ * @param ascii The ASCII file string builder
+ * @param level The indentation level
+ */
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ NSObject[] array = allObjects();
+ ascii.append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
+ int indexOfLastNewLine = ascii.lastIndexOf(NEWLINE);
+ for (int i = 0; i < array.length; i++) {
+ Class<?> objClass = array[i].getClass();
+ if ((objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class))
+ && indexOfLastNewLine != ascii.length()) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ array[i].toASCII(ascii, level + 1);
+ } else {
+ if (i != 0)
+ ascii.append(" ");
+ array[i].toASCII(ascii, 0);
+ }
+
+ if (i != array.length - 1)
+ ascii.append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
+
+ if (ascii.length() - indexOfLastNewLine > ASCII_LINE_LENGTH) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ }
+ }
+ ascii.append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
+ }
+
+ /**
+ * Returns the ASCII representation of this set according to the GnuStep format.
+ * There is no official ASCII representation for sets.
+ * In this implementation sets are represented as arrays.
+ *
+ * @param ascii The ASCII file string builder
+ * @param level The indentation level
+ */
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ NSObject[] array = allObjects();
+ ascii.append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
+ int indexOfLastNewLine = ascii.lastIndexOf(NEWLINE);
+ for (int i = 0; i < array.length; i++) {
+ Class<?> objClass = array[i].getClass();
+ if ((objClass.equals(NSDictionary.class) || objClass.equals(NSArray.class) || objClass.equals(NSData.class))
+ && indexOfLastNewLine != ascii.length()) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ array[i].toASCIIGnuStep(ascii, level + 1);
+ } else {
+ if (i != 0)
+ ascii.append(" ");
+ array[i].toASCIIGnuStep(ascii, 0);
+ }
+
+ if (i != array.length - 1)
+ ascii.append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
+
+ if (ascii.length() - indexOfLastNewLine > ASCII_LINE_LENGTH) {
+ ascii.append(NEWLINE);
+ indexOfLastNewLine = ascii.length();
+ }
+ }
+ ascii.append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
+ }
+
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/NSString.java b/third_party/java/dd_plist/java/com/dd/plist/NSString.java
new file mode 100644
index 0000000000..d692bbfc44
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/NSString.java
@@ -0,0 +1,270 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt
+ *
+ * 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.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+/**
+ * A NSString contains a string.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class NSString extends NSObject implements Comparable<Object> {
+
+ private String content;
+
+ /**
+ * Creates an NSString from its binary representation.
+ *
+ * @param bytes The binary representation.
+ * @param encoding The encoding of the binary representation, the name of a supported charset.
+ * @throws UnsupportedEncodingException
+ * @see java.lang.String
+ */
+ public NSString(byte[] bytes, String encoding) throws UnsupportedEncodingException {
+ content = new String(bytes, encoding);
+ }
+
+ /**
+ * Creates a NSString from a string.
+ *
+ * @param string The string that will be contained in the NSString.
+ */
+ public NSString(String string) {
+ content = string;
+ }
+
+ /**
+ * Gets this strings content.
+ *
+ * @return This NSString as Java String object.
+ */
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * Sets the contents of this string.
+ *
+ * @param c The new content of this string object.
+ */
+ public void setContent(String c) {
+ content = c;
+ }
+
+ /**
+ * Appends a string to this string.
+ *
+ * @param s The string to append.
+ */
+ public void append(NSString s) {
+ append(s.getContent());
+ }
+
+ /**
+ * Appends a string to this string.
+ *
+ * @param s The string to append.
+ */
+ public void append(String s) {
+ content += s;
+ }
+
+ /**
+ * Prepends a string to this string.
+ *
+ * @param s The string to prepend.
+ */
+ public void prepend(String s) {
+ content = s + content;
+ }
+
+ /**
+ * Prepends a string to this string.
+ *
+ * @param s The string to prepend.
+ */
+ public void prepend(NSString s) {
+ prepend(s.getContent());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof NSString)) return false;
+ return content.equals(((NSString) obj).content);
+ }
+
+ @Override
+ public int hashCode() {
+ return content.hashCode();
+ }
+
+ /**
+ * The textual representation of this NSString.
+ *
+ * @return The NSString's contents.
+ */
+ @Override
+ public String toString() {
+ return content;
+ }
+
+ private static CharsetEncoder asciiEncoder, utf16beEncoder, utf8Encoder;
+
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<string>");
+
+ //Make sure that the string is encoded in UTF-8 for the XML output
+ synchronized (NSString.class) {
+ if (utf8Encoder == null)
+ utf8Encoder = Charset.forName("UTF-8").newEncoder();
+ else
+ utf8Encoder.reset();
+
+ try {
+ ByteBuffer byteBuf = utf8Encoder.encode(CharBuffer.wrap(content));
+ byte[] bytes = new byte[byteBuf.remaining()];
+ byteBuf.get(bytes);
+ content = new String(bytes, "UTF-8");
+ } catch (Exception ex) {
+ throw new RuntimeException("Could not encode the NSString into UTF-8: " + String.valueOf(ex.getMessage()));
+ }
+ }
+
+ //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
+ //contain the characters < or &. Also the > character should be escaped.
+ if (content.contains("&") || content.contains("<") || content.contains(">")) {
+ xml.append("<![CDATA[");
+ xml.append(content.replaceAll("]]>", "]]]]><![CDATA[>"));
+ xml.append("]]>");
+ } else {
+ xml.append(content);
+ }
+ xml.append("</string>");
+ }
+
+
+ @Override
+ public void toBinary(BinaryPropertyListWriter out) throws IOException {
+ CharBuffer charBuf = CharBuffer.wrap(content);
+ int kind;
+ ByteBuffer byteBuf;
+ synchronized (NSString.class) {
+ if (asciiEncoder == null)
+ asciiEncoder = Charset.forName("ASCII").newEncoder();
+ else
+ asciiEncoder.reset();
+
+ if (asciiEncoder.canEncode(charBuf)) {
+ kind = 0x5; // standard ASCII
+ byteBuf = asciiEncoder.encode(charBuf);
+ } else {
+ if (utf16beEncoder == null)
+ utf16beEncoder = Charset.forName("UTF-16BE").newEncoder();
+ else
+ utf16beEncoder.reset();
+
+ kind = 0x6; // UTF-16-BE
+ byteBuf = utf16beEncoder.encode(charBuf);
+ }
+ }
+ byte[] bytes = new byte[byteBuf.remaining()];
+ byteBuf.get(bytes);
+ out.writeIntHeader(kind, content.length());
+ out.write(bytes);
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append("\"");
+ //According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
+ //non-ASCII characters are not escaped but simply written into the
+ //file, thus actually violating the ASCII plain text format.
+ //We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings.
+ ascii.append(escapeStringForASCII(content));
+ ascii.append("\"");
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append("\"");
+ ascii.append(escapeStringForASCII(content));
+ ascii.append("\"");
+ }
+
+ /**
+ * Escapes a string for use in ASCII property lists.
+ *
+ * @param s The unescaped string.
+ * @return The escaped string.
+ */
+ static String escapeStringForASCII(String s) {
+ String out = "";
+ char[] cArray = s.toCharArray();
+ for (int i = 0; i < cArray.length; i++) {
+ char c = cArray[i];
+ if (c > 127) {
+ //non-ASCII Unicode
+ out += "\\U";
+ String hex = Integer.toHexString(c);
+ while (hex.length() < 4)
+ hex = "0" + hex;
+ out += hex;
+ } else if (c == '\\') {
+ out += "\\\\";
+ } else if (c == '\"') {
+ out += "\\\"";
+ } else if (c == '\b') {
+ out += "\\b";
+ } else if (c == '\n') {
+ out += "\\n";
+ } else if (c == '\r') {
+ out += "\\r";
+ } else if (c == '\t') {
+ out += "\\t";
+ } else {
+ out += c;
+ }
+ }
+ return out;
+ }
+
+ public int compareTo(Object o) {
+ if (o instanceof NSString) {
+ return getContent().compareTo(((NSString) o).getContent());
+ } else if (o instanceof String) {
+ return getContent().compareTo(((String) o));
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/PropertyListFormatException.java b/third_party/java/dd_plist/java/com/dd/plist/PropertyListFormatException.java
new file mode 100644
index 0000000000..13988a0832
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/PropertyListFormatException.java
@@ -0,0 +1,40 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+ * 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;
+
+/**
+ * A PropertyListFormatException is thrown by the various property list format parsers
+ * when an error in the format of the given property list is encountered.
+ * @author Daniel Dreibrodt
+ */
+public class PropertyListFormatException extends Exception {
+
+ /**
+ * Creates a new exception with the given message.
+ * @param message A message containing information about the nature of the exception.
+ */
+ public PropertyListFormatException(String message) {
+ super(message);
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/PropertyListParser.java b/third_party/java/dd_plist/java/com/dd/plist/PropertyListParser.java
new file mode 100644
index 0000000000..66cac14c98
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/PropertyListParser.java
@@ -0,0 +1,383 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011-2014 Daniel Dreibrodt
+ *
+ * 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 org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.text.ParseException;
+
+/**
+ * This class provides methods to parse property lists. It can handle files,
+ * input streams and byte arrays. All known property list formats are supported.
+ * <p/>
+ * This class also provides methods to save and convert property lists.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class PropertyListParser {
+
+ private static final int TYPE_XML = 0;
+ private static final int TYPE_BINARY = 1;
+ private static final int TYPE_ASCII = 2;
+ private static final int TYPE_ERROR_BLANK = 10;
+ private static final int TYPE_ERROR_UNKNOWN = 11;
+
+ /**
+ * Prevent instantiation.
+ */
+ protected PropertyListParser() {
+ /** empty **/
+ }
+
+ /**
+ * Determines the type of a property list by means of the first bytes of its data
+ * @param dataBeginning The very first bytes of data of the property list (minus any whitespace) as a string
+ * @return The type of the property list
+ */
+ private static int determineType(String dataBeginning) {
+ dataBeginning = dataBeginning.trim();
+ if(dataBeginning.length() == 0) {
+ return TYPE_ERROR_BLANK;
+ }
+ if(dataBeginning.startsWith("bplist")) {
+ return TYPE_BINARY;
+ }
+ if(dataBeginning.startsWith("(") || dataBeginning.startsWith("{") || dataBeginning.startsWith("/")) {
+ return TYPE_ASCII;
+ }
+ if(dataBeginning.startsWith("<")) {
+ return TYPE_XML;
+ }
+ return TYPE_ERROR_UNKNOWN;
+ }
+
+ /**
+ * Determines the type of a property list by means of the first bytes of its data
+ * @param bytes The very first bytes of data of the property list (minus any whitespace)
+ * @return The type of the property list
+ */
+ private static int determineType(byte[] bytes) {
+ //Skip any possible whitespace at the beginning of the file
+ int offset = 0;
+ while(offset < bytes.length && bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' || bytes[offset] == '\f') {
+ offset++;
+ }
+ return determineType(new String(bytes, offset, Math.min(8, bytes.length - offset)));
+ }
+
+ /**
+ * Determines the type of a property list by means of the first bytes of its data
+ * @param is An input stream pointing to the beginning of the property list data.
+ * If the stream supports marking it will be reset to the beginning of the property
+ * list data after the type has been determined.
+ * @return The type of the property list
+ */
+ private static int determineType(InputStream is) throws IOException {
+ //Skip any possible whitespace at the beginning of the file
+ byte[] magicBytes = new byte[8];
+ int b;
+ do {
+ if(is.markSupported())
+ is.mark(16);
+ b = is.read();
+ }
+ while(b != -1 && b == ' ' || b == '\t' || b == '\r' || b == '\n' || b == '\f');
+ magicBytes[0] = (byte)b;
+ int read = is.read(magicBytes, 1, 7);
+ int type = determineType(new String(magicBytes, 0, read));
+ if(is.markSupported())
+ is.reset();
+ return type;
+ }
+
+ /**
+ * Reads all bytes from an InputStream and stores them in an array, up to
+ * a maximum count.
+ *
+ * @param in The InputStream pointing to the data that should be stored in the array.
+ */
+ protected static byte[] readAll(InputStream in) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] buf = new byte[512];
+ int read;
+ while ((read = in.read(buf)) > 0) {
+ outputStream.write(buf, 0, read);
+ }
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Parses a property list from a file.
+ *
+ * @param filePath Path to the property list file.
+ * @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
+ * @throws Exception If an error occurred while parsing.
+ */
+ public static NSObject parse(String filePath) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
+ return parse(new File(filePath));
+ }
+
+ /**
+ * Parses a property list from a file.
+ *
+ * @param f The property list file.
+ * @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
+ * @throws Exception If an error occurred while parsing.
+ */
+ public static NSObject parse(File f) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
+ FileInputStream fis = new FileInputStream(f);
+ int type = determineType(fis);
+ fis.close();
+ switch(type) {
+ case TYPE_BINARY:
+ return BinaryPropertyListParser.parse(f);
+ case TYPE_XML:
+ return XMLPropertyListParser.parse(f);
+ case TYPE_ASCII:
+ return ASCIIPropertyListParser.parse(f);
+ default:
+ throw new PropertyListFormatException("The given file is not a property list of a supported format.");
+ }
+ }
+
+ /**
+ * Parses a property list from a byte array.
+ *
+ * @param bytes The property list data as a byte array.
+ * @return The root object in the property list. This is usually a NSDictionary but can also be a NSArray.
+ * @throws Exception If an error occurred while parsing.
+ */
+ public static NSObject parse(byte[] bytes) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
+ switch(determineType(bytes)) {
+ case TYPE_BINARY:
+ return BinaryPropertyListParser.parse(bytes);
+ case TYPE_XML:
+ return XMLPropertyListParser.parse(bytes);
+ case TYPE_ASCII:
+ return ASCIIPropertyListParser.parse(bytes);
+ default:
+ throw new PropertyListFormatException("The given data is not a property list of a supported format.");
+ }
+ }
+
+ /**
+ * Parses a property list from an InputStream.
+ *
+ * @param is The InputStream delivering the property list data.
+ * @return The root object of the property list. This is usually a NSDictionary but can also be a NSArray.
+ * @throws Exception If an error occurred while parsing.
+ */
+ public static NSObject parse(InputStream is) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
+ return parse(readAll(is));
+ }
+
+ /**
+ * Saves a property list with the given object as root into a XML file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsXML(NSObject root, File out) throws IOException {
+ File parent = out.getParentFile();
+ if (!parent.exists())
+ parent.mkdirs();
+ FileOutputStream fous = new FileOutputStream(out);
+ saveAsXML(root, fous);
+ fous.close();
+ }
+
+ /**
+ * Saves a property list with the given object as root in XML format into an output stream.
+ *
+ * @param root The root object.
+ * @param out The output stream.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsXML(NSObject root, OutputStream out) throws IOException {
+ OutputStreamWriter w = new OutputStreamWriter(out, "UTF-8");
+ w.write(root.toXMLPropertyList());
+ w.close();
+ }
+
+ /**
+ * Converts a given property list file into the OS X and iOS XML format.
+ *
+ * @param in The source file.
+ * @param out The target file.
+ * @throws Exception When an error occurs during parsing or converting.
+ */
+ public static void convertToXml(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
+ NSObject root = parse(in);
+ saveAsXML(root, out);
+ }
+
+ /**
+ * Saves a property list with the given object as root into a binary file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsBinary(NSObject root, File out) throws IOException {
+ File parent = out.getParentFile();
+ if (!parent.exists())
+ parent.mkdirs();
+ BinaryPropertyListWriter.write(out, root);
+ }
+
+ /**
+ * Saves a property list with the given object as root in binary format into an output stream.
+ *
+ * @param root The root object.
+ * @param out The output stream.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsBinary(NSObject root, OutputStream out) throws IOException {
+ BinaryPropertyListWriter.write(out, root);
+ }
+
+ /**
+ * Converts a given property list file into the OS X and iOS binary format.
+ *
+ * @param in The source file.
+ * @param out The target file.
+ * @throws Exception When an error occurs during parsing or converting.
+ */
+ public static void convertToBinary(File in, File out) throws IOException, ParserConfigurationException, ParseException, SAXException, PropertyListFormatException {
+ NSObject root = parse(in);
+ saveAsBinary(root, out);
+ }
+
+ /**
+ * Saves a property list with the given object as root into a ASCII file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsASCII(NSDictionary root, File out) throws IOException {
+ File parent = out.getParentFile();
+ if (!parent.exists())
+ parent.mkdirs();
+ OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
+ w.write(root.toASCIIPropertyList());
+ w.close();
+ }
+
+ /**
+ * Saves a property list with the given object as root into a ASCII file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsASCII(NSArray root, File out) throws IOException {
+ OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
+ w.write(root.toASCIIPropertyList());
+ w.close();
+ }
+
+ /**
+ * Converts a given property list file into ASCII format.
+ *
+ * @param in The source file.
+ * @param out The target file.
+ * @throws Exception When an error occurs during parsing or converting.
+ */
+ public static void convertToASCII(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
+ NSObject root = parse(in);
+ if(root instanceof NSDictionary) {
+ saveAsASCII((NSDictionary) root, out);
+ }
+ else if(root instanceof NSArray) {
+ saveAsASCII((NSArray) root, out);
+ }
+ else {
+ throw new PropertyListFormatException("The root of the given input property list "
+ + "is neither a Dictionary nor an Array!");
+ }
+ }
+
+ /**
+ * Saves a property list with the given object as root into a ASCII file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsGnuStepASCII(NSDictionary root, File out) throws IOException {
+ File parent = out.getParentFile();
+ if (!parent.exists())
+ parent.mkdirs();
+ OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
+ w.write(root.toGnuStepASCIIPropertyList());
+ w.close();
+ }
+
+ /**
+ * Saves a property list with the given object as root into a ASCII file.
+ *
+ * @param root The root object.
+ * @param out The output file.
+ * @throws IOException When an error occurs during the writing process.
+ */
+ public static void saveAsGnuStepASCII(NSArray root, File out) throws IOException {
+ File parent = out.getParentFile();
+ if (!parent.exists())
+ parent.mkdirs();
+ OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(out), "ASCII");
+ w.write(root.toGnuStepASCIIPropertyList());
+ w.close();
+ }
+
+ /**
+ * Converts a given property list file into ASCII format.
+ *
+ * @param in The source file.
+ * @param out The target file.
+ * @throws Exception When an error occurs during parsing or converting.
+ */
+ public static void convertToGnuStepASCII(File in, File out) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
+ NSObject root = parse(in);
+ if(root instanceof NSDictionary) {
+ saveAsGnuStepASCII((NSDictionary) root, out);
+ }
+ else if(root instanceof NSArray) {
+ saveAsGnuStepASCII((NSArray) root, out);
+ }
+ else {
+ throw new PropertyListFormatException("The root of the given input property list "
+ + "is neither a Dictionary nor an Array!");
+ }
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/UID.java b/third_party/java/dd_plist/java/com/dd/plist/UID.java
new file mode 100644
index 0000000000..9dde01cd05
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/UID.java
@@ -0,0 +1,93 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2011 Daniel Dreibrodt
+ *
+ * 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.IOException;
+
+/**
+ * A UID. Only found in binary property lists that are keyed archives.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class UID extends NSObject {
+
+ private byte[] bytes;
+ private String name;
+
+ public UID(String name, byte[] bytes) {
+ this.name = name;
+ this.bytes = bytes;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * There is no XML representation specified for UIDs.
+ * In this implementation UIDs are represented as strings in the XML output.
+ *
+ * @param xml The xml StringBuilder
+ * @param level The indentation level
+ */
+ @Override
+ void toXML(StringBuilder xml, int level) {
+ indent(xml, level);
+ xml.append("<string>");
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ if (b < 16)
+ xml.append("0");
+ xml.append(Integer.toHexString(b));
+ }
+ xml.append("</string>");
+ }
+
+ @Override
+ void toBinary(BinaryPropertyListWriter out) throws IOException {
+ out.write(0x80 + bytes.length - 1);
+ out.write(bytes);
+ }
+
+ @Override
+ protected void toASCII(StringBuilder ascii, int level) {
+ indent(ascii, level);
+ ascii.append("\"");
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ if (b < 16)
+ ascii.append("0");
+ ascii.append(Integer.toHexString(b));
+ }
+ ascii.append("\"");
+ }
+
+ @Override
+ protected void toASCIIGnuStep(StringBuilder ascii, int level) {
+ toASCII(ascii, level);
+ }
+}
diff --git a/third_party/java/dd_plist/java/com/dd/plist/XMLPropertyListParser.java b/third_party/java/dd_plist/java/com/dd/plist/XMLPropertyListParser.java
new file mode 100644
index 0000000000..14776ea0b8
--- /dev/null
+++ b/third_party/java/dd_plist/java/com/dd/plist/XMLPropertyListParser.java
@@ -0,0 +1,273 @@
+/*
+ * plist - An open source library to parse and generate property lists
+ * Copyright (C) 2014 Daniel Dreibrodt
+ *
+ * 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 org.w3c.dom.*;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses XML property lists.
+ *
+ * @author Daniel Dreibrodt
+ */
+public class XMLPropertyListParser {
+
+ /**
+ * Instantiation is prohibited by outside classes.
+ */
+ protected XMLPropertyListParser() {
+ /** empty **/
+ }
+
+ private static DocumentBuilderFactory docBuilderFactory = null;
+
+ /**
+ * Initialize the document builder factory so that it can be reuused and does not need to
+ * be reinitialized for each new parsing.
+ */
+ private static synchronized void initDocBuilderFactory() {
+ docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setIgnoringComments(true);
+ docBuilderFactory.setCoalescing(true);
+ }
+
+ /**
+ * Gets a DocumentBuilder to parse a XML property list.
+ * As DocumentBuilders are not thread-safe a new DocBuilder is generated for each request.
+ *
+ * @return A new DocBuilder that can parse property lists w/o an internet connection.
+ */
+ private static synchronized DocumentBuilder getDocBuilder() throws ParserConfigurationException {
+ if (docBuilderFactory == null)
+ initDocBuilderFactory();
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ docBuilder.setEntityResolver(new EntityResolver() {
+ public InputSource resolveEntity(String publicId, String systemId) {
+ if ("-//Apple Computer//DTD PLIST 1.0//EN".equals(publicId) || // older publicId
+ "-//Apple//DTD PLIST 1.0//EN".equals(publicId)) { // newer publicId
+ // return a dummy, zero length DTD so we don't have to fetch
+ // it from the network.
+ return new InputSource(new ByteArrayInputStream(new byte[0]));
+ }
+ return null;
+ }
+ });
+ return docBuilder;
+ }
+
+ /**
+ * Parses a XML property list file.
+ *
+ * @param f The XML property list file.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ * @see javax.xml.parsers.DocumentBuilder#parse(java.io.File)
+ */
+ public static NSObject parse(File f) throws ParseException, IOException, PropertyListFormatException, SAXException, ParserConfigurationException {
+ DocumentBuilder docBuilder = getDocBuilder();
+
+ Document doc = docBuilder.parse(f);
+
+ return parseDocument(doc);
+ }
+
+ /**
+ * Parses a XML property list from a byte array.
+ *
+ * @param bytes The byte array containing the property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ */
+ public static NSObject parse(final byte[] bytes) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ return parse(bis);
+ }
+
+ /**
+ * Parses a XML property list from an input stream.
+ *
+ * @param is The input stream pointing to the property list's data.
+ * @return The root object of the property list. This is usally a NSDictionary but can also be a NSArray.
+ * @throws Exception When an error occurs during parsing.
+ * @see javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream)
+ */
+ public static NSObject parse(InputStream is) throws ParserConfigurationException, IOException, SAXException, PropertyListFormatException, ParseException {
+ DocumentBuilder docBuilder = getDocBuilder();
+
+ Document doc = docBuilder.parse(is);
+
+ return parseDocument(doc);
+ }
+
+ /**
+ * Parses the XML document by generating the appropriate NSObjects for each XML node.
+ *
+ * @param doc The XML document.
+ * @return The root NSObject of the property list contained in the XML document.
+ * @throws Exception If an error occured during parsing.
+ */
+ private static NSObject parseDocument(Document doc) throws PropertyListFormatException, IOException, ParseException {
+ DocumentType docType = doc.getDoctype();
+ if (docType == null) {
+ if (!doc.getDocumentElement().getNodeName().equals("plist")) {
+ throw new UnsupportedOperationException("The given XML document is not a property list.");
+ }
+ } else if (!docType.getName().equals("plist")) {
+ throw new UnsupportedOperationException("The given XML document is not a property list.");
+ }
+
+ Node rootNode;
+
+ if (doc.getDocumentElement().getNodeName().equals("plist")) {
+ //Root element wrapped in plist tag
+ List<Node> rootNodes = filterElementNodes(doc.getDocumentElement().getChildNodes());
+ if (rootNodes.isEmpty()) {
+ throw new PropertyListFormatException("The given XML property list has no root element!");
+ } else if (rootNodes.size() == 1) {
+ rootNode = rootNodes.get(0);
+ } else {
+ throw new PropertyListFormatException("The given XML property list has more than one root element!");
+ }
+ } else {
+ //Root NSObject not wrapped in plist-tag
+ rootNode = doc.getDocumentElement();
+ }
+
+ return parseObject(rootNode);
+ }
+
+ /**
+ * Parses a node in the XML structure and returns the corresponding NSObject
+ *
+ * @param n The XML node.
+ * @return The corresponding NSObject.
+ * @throws Exception If an error occured during parsing the node.
+ */
+ private static NSObject parseObject(Node n) throws ParseException, IOException {
+ String type = n.getNodeName();
+ if (type.equals("dict")) {
+ NSDictionary dict = new NSDictionary();
+ List<Node> children = filterElementNodes(n.getChildNodes());
+ for (int i = 0; i < children.size(); i += 2) {
+ Node key = children.get(i);
+ Node val = children.get(i + 1);
+
+ String keyString = getNodeTextContents(key);
+
+ dict.put(keyString, parseObject(val));
+ }
+ return dict;
+ } else if (type.equals("array")) {
+ List<Node> children = filterElementNodes(n.getChildNodes());
+ NSArray array = new NSArray(children.size());
+ for (int i = 0; i < children.size(); i++) {
+ array.setValue(i, parseObject(children.get(i)));
+ }
+ return array;
+ } else if (type.equals("true")) {
+ return new NSNumber(true);
+ } else if (type.equals("false")) {
+ return new NSNumber(false);
+ } else if (type.equals("integer")) {
+ return new NSNumber(getNodeTextContents(n));
+ } else if (type.equals("real")) {
+ return new NSNumber(getNodeTextContents(n));
+ } else if (type.equals("string")) {
+ return new NSString(getNodeTextContents(n));
+ } else if (type.equals("data")) {
+ return new NSData(getNodeTextContents(n));
+ } else if (type.equals("date")) {
+ return new NSDate(getNodeTextContents(n));
+ }
+ return null;
+ }
+
+ /**
+ * Returns all element nodes that are contained in a list of nodes.
+ *
+ * @param list The list of nodes to search.
+ * @return The sublist containing only nodes representing actual elements.
+ */
+ private static List<Node> filterElementNodes(NodeList list) {
+ List<Node> result = new ArrayList<Node>(list.getLength());
+ for (int i = 0; i < list.getLength(); i++) {
+ if (list.item(i).getNodeType() == Node.ELEMENT_NODE) {
+ result.add(list.item(i));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a node's text content.
+ * This method will return the text value represented by the node's direct children.
+ * If the given node is a TEXT or CDATA node, then its value is returned.
+ *
+ * @param n The node.
+ * @return The node's text content.
+ */
+ private static String getNodeTextContents(Node n) {
+ if (n.getNodeType() == Node.TEXT_NODE || n.getNodeType() == Node.CDATA_SECTION_NODE) {
+ Text txtNode = (Text) n;
+ String content = txtNode.getWholeText(); //This concatenates any adjacent text/cdata/entity nodes
+ if (content == null)
+ return "";
+ else
+ return content;
+ } else {
+ if (n.hasChildNodes()) {
+ NodeList children = n.getChildNodes();
+
+ for (int i = 0; i < children.getLength(); i++) {
+ //Skip any non-text nodes, like comments or entities
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
+ Text txtNode = (Text) child;
+ String content = txtNode.getWholeText(); //This concatenates any adjacent text/cdata/entity nodes
+ if (content == null)
+ return "";
+ else
+ return content;
+ }
+ }
+
+ return "";
+ } else {
+ return "";
+ }
+ }
+ }
+}