package gizmoball.property;

import gizmoball.NotImplementedException;

/**
 * A property of a {@link Propertyified} object.  This is a name-value
 * pair, where the value has a corresponding type.
 *
 * <p>Convenience methods are provided for getting the unboxed value
 * of the property as certain standard types.
 *
 * <p>Note that properties are always some type of Object, not
 * primitive types, even if the corresponding get/set methods of the
 * original Propertyified object return primitives.  It will be
 * automatically boxed into the corresponding object type.
 *
 * <p>Also, this object is immutable and will not change to reflect
 * new values of the property if they change in the original object.
 * It is instead a "snapshot" of the property when {@link
 * Propertyified#getProperty(String)} was called.
 *
 * @author Austin Clements
 * @version 1.0
 *
 * @specfield object : Object // The object with this property
 * @specfield name : String   // The name of the property
 * @specfield value : Object  // The value of the property
 * @specfield type : Class    // The type of the value
 */
public class Property {
    /* Abstraction function
     *   Trivial       
     */
    
    private Propertyified object;
    private String name;
    private Object value;
    private Class type;

    /* Representation invariant
     *   None
     */

    /**
     * Constructs a property object for the specified property.  This
     * is package private because it is only meant to be constructed
     * by getProperty.
     *
     * @param object the object that this property was gotten from
     * @param name the name of this property (with the corresponding
     * get/set methods)
     * @param value the value of this property
     * @param type the type of the value
     * @modifies this
     * @effects constructs a new Property object
     */
    Property(Propertyified object, String name, Object value, Class type) {
        this.object = object;
        this.name = name;
        this.value = value;
        this.type = type;
    }

    /**
     * Gets the object from which this property was gotten.  This
     * corresponds to the object on which getProperty was called.
     *
     * @return the object this property belongs to
     */
    public Propertyified getObject() {
        return object;
    }
    
    /**
     * Gets the name of this property.  This will correspond with the
     * get/set method of the original object.
     *
     * @return property name
     */
    public String getName() {
        return name;
    }

    /**
     * Gets the value of this property.
     *
     * @return property value
     */
    public Object getValue() {
        return value;
    }

    /**
     * Gets the type of this property.  If the underlying property is
     * a primitive type, it will appear as the corresponding object
     * type here.
     *
     * @return property type
     */
    public Class getType() {
        return type;
    }

    /**
     * Gets the value of this property as a boolean.
     *
     * @return property value
     * @throws UnsupportedOperationException if the property is not a
     * boolean or was originally a Boolean that was null
     */
    public boolean getBooleanValue()
        throws UnsupportedOperationException {
        if (value == null) {
            throw new UnsupportedOperationException("Property is null");
        }
        if (!Boolean.class.isAssignableFrom(type)) {
            throw new UnsupportedOperationException
                ("Property is not a boolean");
        }

        return ((Boolean)value).booleanValue();
    }

    /**
     * Gets the value of this property as an integer.
     *
     * @return property value
     * @throws UnsupportedOperationException if the property is not an
     * integer or was originally an Integer that was null
     */
    public int getIntegerValue()
        throws UnsupportedOperationException {
        if (value == null) {
            throw new UnsupportedOperationException("Property is null");
        }
        if (!Integer.class.isAssignableFrom(type)) {
            throw new UnsupportedOperationException
                ("Property is not an integer");
        }

        return ((Integer)value).intValue();
    }

    /**
     * Gets the value of this property as a double.
     *
     * @return property value
     * @throws UnsupportedOperationException if the property is not a
     * double or was originally a Double that was null
     */
    public double getDoubleValue()
        throws UnsupportedOperationException {
        if (value == null) {
            throw new UnsupportedOperationException("Property is null");
        }
        if (!Double.class.isAssignableFrom(type)) {
            throw new UnsupportedOperationException
                ("Property is not a double");
        }

        return ((Double)value).doubleValue();
    }

    /**
     * Gets the value of this property as a string.
     *
     * @return property value
     * @throws UnsupportedOperationException if the property is not a
     * string
     */
    public String getStringValue()
        throws UnsupportedOperationException {
        if (!String.class.isAssignableFrom(type)) {
            throw new UnsupportedOperationException
                ("Property is not a String");
        }

        return (String)value;
    }
}
