// Copyright 2008-2012 severally by the contributors
//
// 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 net.sf.practicalxml.converter;

import org.w3c.dom.Element;

import net.sf.practicalxml.DomUtil;


/**
 *  A runtime exception thrown for any conversion error. Will always have a
 *  message, and typically contains a wrapped exception. Also contains
 *  information about the field that caused the exception:
 *  <ul>
 *  <li> During Bean->XML conversion, contains the dot-delimited path of the
 *       field that caused the exception.
 *  <li> During XML->Bean conversion, contains the absolute XPath of the node
 *       that caused the exception.
 *  </ul>
 *
 *  @since 1.1
 */
public class ConversionException
extends RuntimeException
{
    private static final long serialVersionUID = 2L;

    private String _baseMessage;
    private String _field;
    private String _xpath;


    /**
     *  This constructor is called from code that is not location-aware.
     *  Instances from this constructor will typically be caught by code
     *  that is, and rethrown in a location-aware instance.
     *
     *  @param  message The base message describing the exception.
     */
    public ConversionException(String message)
    {
        super(message);
        _baseMessage = message;
    }


    /**
     *  This constructor is called from code that is not location-aware.
     *  Instances from this constructor will typically be caught by code
     *  that is, and rethrown in a location-aware instance.
     *
     *  @param  message The base message describing the exception.
     *  @param  cause   The underlying cause of the exception.
     */
    public ConversionException(String message, Throwable cause)
    {
        super(message, cause);
        _baseMessage = message;
    }


    /**
     *  This constructor is invoked from XML->Bean conversions, where the
     *  converter is aware of its location.
     *
     *  @param  message The base message describing the exception.
     *  @param  elem    The DOM element where the failure took place.
     */
    public ConversionException(String message, Element elem)
    {
        this(message, elem, null);
    }


    /**
     *  This constructor is invoked from XML->Bean conversions, where the
     *  converter is aware of its location.
     *
     *  @param  message The base message describing the exception.
     *  @param  elem    The DOM element where the failure took place.
     *  @param  cause   The underlying cause of the exception.
     */
    public ConversionException(String message, Element elem, Throwable cause)
    {
        super(constructMessage(message, DomUtil.getAbsolutePath(elem)), cause);
        _xpath = DomUtil.getAbsolutePath(elem);
    }


    /**
     *  This constructor is invoked from Bean->XML conversions, where the
     *  converter is aware of its location.
     *
     *  @param  message The base message describing the exception.
     *  @param  field   The name of the field where the failure occurred.
     */
    public ConversionException(String message, String field)
    {
        this(message, field, null);
    }


    /**
     *  This constructor is invoked from Bean->XML conversions, where the
     *  converter is aware of its location.
     *
     *  @param  message The base message describing the exception.
     *  @param  field   The name of the field where the failure occurred.
     *  @param  cause   The underlying cause of the exception.
     */
    public ConversionException(String message, String field, Throwable cause)
    {
        super(constructMessage(message, field), cause);
        _baseMessage = message;
        _field = field;
    }


    /**
     *  This constructor is  invoked from Bean->XML conversions, to prepend
     *  a field component to the location before rethrowing the exception.
     *
     *  @param  source  The original exception.
     *  @param  field   The name of the field component to prepend.
     */
    public ConversionException(ConversionException source, String field)
    {
        this(source._baseMessage, constructField(source._field, field), source.getCause());
        setStackTrace(source.getStackTrace());
    }


//----------------------------------------------------------------------------
//  Public methods
//----------------------------------------------------------------------------

    /**
     *  Returns the XPath of the node causing this exception, <code>null</code>
     *  if unknown.
     */
    public String getXPath()
    {
        return _xpath;
    }


    /**
     *  Returns the name of the field where this exception occurred,
     *  <code>null</code> if unknown.
     */
    public String getField()
    {
        return _field;
    }


//----------------------------------------------------------------------------
//  Internals
//----------------------------------------------------------------------------

    private static String constructMessage(String baseMessage, String addition)
    {
        StringBuilder buf = new StringBuilder(baseMessage.length() + addition.length() + 3);
        buf.append(baseMessage).append(": ").append(addition);
        return buf.toString();
    }


    private static String constructField(String sourceField, String field)
    {
        if (sourceField == null)
            return field;

        StringBuilder buf = new StringBuilder(sourceField.length() + field.length() + 2);
        buf.append(field).append(".").append(sourceField);
        return buf.toString();
    }
}
