/* * 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 of only this number * @param type The type of number * @see #INTEGER * @see #REAL */ public NSNumber(byte[] bytes, int type){ this(bytes, 0, bytes.length, type); } /** * Parses integers and real numbers from their binary representation. * <i>Note: real numbers are not yet supported.</i> * * @param bytes array of bytes that contains this number's binary representation * @param startIndex int with the position where to start reading from the byte array * @param endIndex int with the position where to end reading from the byte array * @param type The type of number * @see #INTEGER * @see #REAL */ public NSNumber(byte[] bytes, final int startIndex, final int endIndex, final int type){ switch (type) { case INTEGER: { doubleValue = longValue = BinaryPropertyListParser.parseLong(bytes, startIndex, endIndex); break; } case REAL: { doubleValue = BinaryPropertyListParser.parseDouble(bytes, startIndex, endIndex); 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, <code>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; } } }