//  Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry.binding;

import java.util.HashMap;
import java.util.Map;

import org.apache.tapestry.BindingException;
import org.apache.tapestry.IBinding;
import org.apache.tapestry.ILocation;
import org.apache.tapestry.Tapestry;

/**
 *  Base class for {@link IBinding} implementations.
 *
 * @author Howard Lewis Ship
 * @version $Id: AbstractBinding.java 243791 2004-02-19 17:38:13Z hlship $
 * 
 **/

public abstract class AbstractBinding implements IBinding
{
    /** @since 3.0 **/

    private ILocation _location;

    /**
     *  A mapping from primitive types to wrapper types.
     * 
     **/

    private static final Map PRIMITIVE_TYPES = new HashMap();

    static {
        PRIMITIVE_TYPES.put(boolean.class, Boolean.class);
        PRIMITIVE_TYPES.put(byte.class, Byte.class);
        PRIMITIVE_TYPES.put(char.class, Character.class);
        PRIMITIVE_TYPES.put(short.class, Short.class);
        PRIMITIVE_TYPES.put(int.class, Integer.class);
        PRIMITIVE_TYPES.put(long.class, Long.class);
        PRIMITIVE_TYPES.put(float.class, Float.class);
        PRIMITIVE_TYPES.put(double.class, Double.class);
    }

    /** @since 3.0 **/

    protected AbstractBinding(ILocation location)
    {
        _location = location;
    }

    public ILocation getLocation()
    {
        return _location;
    }

    /**
     *  Cooerces the raw value into a true or false, according to the
     *  rules set by {@link Tapestry#evaluateBoolean(Object)}.
     *
     **/

    public boolean getBoolean()
    {
        return Tapestry.evaluateBoolean(getObject());
    }

    public int getInt()
    {
        Object raw;

        raw = getObject();
        if (raw == null)
            throw Tapestry.createNullBindingException(this);

        if (raw instanceof Number)
        {
            return ((Number) raw).intValue();
        }

        if (raw instanceof Boolean)
        {
            return ((Boolean) raw).booleanValue() ? 1 : 0;
        }

        // Save parsing for last.  This may also throw a number format exception.

        return Integer.parseInt((String) raw);
    }

    public double getDouble()
    {
        Object raw;

        raw = getObject();
        if (raw == null)
            throw Tapestry.createNullBindingException(this);

        if (raw instanceof Number)
        {
            return ((Number) raw).doubleValue();
        }

        if (raw instanceof Boolean)
        {
            return ((Boolean) raw).booleanValue() ? 1 : 0;
        }

        // Save parsing for last.  This may also throw a number format exception.

        return Double.parseDouble((String) raw);
    }

    /**
     *  Gets the value for the binding.  If null, returns null,
     *  otherwise, returns the String (<code>toString()</code>) version of
     *  the value.
     *
     **/

    public String getString()
    {
        Object value;

        value = getObject();
        if (value == null)
            return null;

        return value.toString();
    }

    /**
     *  @throws BindingException always.
     *
     **/

    public void setBoolean(boolean value)
    {
        throw createReadOnlyBindingException(this);
    }

    /**
     *  @throws BindingException always.
     *
     **/

    public void setInt(int value)
    {
        throw createReadOnlyBindingException(this);
    }

    /**
     *  @throws BindingException always.
     *
     **/

    public void setDouble(double value)
    {
        throw createReadOnlyBindingException(this);
    }

    /**
     *  @throws BindingException always.
     *
     **/

    public void setString(String value)
    {
        throw createReadOnlyBindingException(this);
    }

    /**
     *  @throws BindingException always.
     *
     **/

    public void setObject(Object value)
    {
        throw createReadOnlyBindingException(this);
    }

    /**
     *  Default implementation: returns true.
     * 
     *  @since 2.0.3
     * 
     **/

    public boolean isInvariant()
    {
        return true;
    }

    public Object getObject(String parameterName, Class type)
    {
        Object result = getObject();

        if (result == null)
            return result;

        Class resultClass = result.getClass();

        if (type.isAssignableFrom(resultClass))
            return result;

        if (type.isPrimitive() && isWrapper(type, resultClass))
            return result;

        String key =
            type.isInterface() ? "AbstractBinding.wrong-interface" : "AbstractBinding.wrong-type";

        String message =
            Tapestry.format(
                key,
                new Object[] { parameterName, result, resultClass.getName(), type.getName()});

        throw new BindingException(message, this);
    }

    public boolean isWrapper(Class primitiveType, Class subjectClass)
    {
        return PRIMITIVE_TYPES.get(primitiveType).equals(subjectClass);
    }

    /** @since 3.0 **/

    protected BindingException createReadOnlyBindingException(IBinding binding)
    {
        return new BindingException(
            Tapestry.getMessage("AbstractBinding.read-only-binding"),
            binding);
    }
}