diff options
author | 2015-02-25 16:45:20 +0100 | |
---|---|---|
committer | 2015-02-25 16:45:20 +0100 | |
commit | d08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch) | |
tree | 5d50963026239ca5aebfb47ea5b8db7e814e57c8 /third_party/java/dd_plist |
Update from Google.
--
MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'third_party/java/dd_plist')
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 (~< 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 ""; + } + } + } +} |