#region License

/*
 * Copyright 2002-2004 the original author or authors.
 *
 * 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.
 */

#endregion

using System;
using Spring.Aop.Framework;
using Spring.Util;

namespace Spring.Transaction.Interceptor
{
	/// <summary>
	/// Rule determining whether or not a given exception (and any subclasses) should
	/// cause a rollback.
	/// </summary>
	/// <remarks>
	/// <p>
	/// Multiple such rules can be applied to determine whether a transaction should commit
	/// or rollback after an exception has been thrown.
	/// </p>
	/// </remarks>
	/// <author>Griffin Caprio (.NET)</author>
	[Serializable]
	public class RollbackRuleAttribute : Attribute
	{
        /// <summary>
        /// Could hold exception, resolving class name but would always require FQN.
        /// This way does multiple string comparisons, but how often do we decide 
        /// whether to roll back a transaction following an exception?
        /// </summary>
		private string _exceptionName;

		/// <summary>
		/// Canonical instance representing default behavior for rolling back on
		/// all <see cref="System.Exception"/>s.
		/// </summary>
		public static RollbackRuleAttribute RollbackOnSystemExceptions
			= new RollbackRuleAttribute(typeof (Exception).Name);

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Transaction.Interceptor.RollbackRuleAttribute"/> class
		/// for the named <paramref name="exceptionName"/>.
		/// </summary>
		/// <param name="exceptionName">The exception name.</param>
		/// <remarks>
		/// <p>
		/// As always, the <paramref name="exceptionName"/> should be the full
		/// assembly qualified version.
		/// </p>
		/// </remarks>
		public RollbackRuleAttribute( string exceptionName )
		{
            AssertUtils.ArgumentHasText(exceptionName, "exceptionName");
			_exceptionName = exceptionName;
		}

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Transaction.Interceptor.RollbackRuleAttribute"/> class
		/// for the suplied <paramref name="exceptionType"/>.
		/// </summary>
		/// <remarks>
		/// <p>
		/// The exception class must be <see cref="System.Exception"/> or a subclass.
		/// </p>
		/// <p>
		/// This is the preferred way to construct a
		/// <see cref="Spring.Transaction.Interceptor.RollbackRuleAttribute"/>,
		/// matching the exception class and subclasses.
		/// </p>
		/// </remarks>
		/// <param name="exceptionType">
		/// The <see cref="System.Exception"/> class that will trigger a rollback.
		/// </param>
		public RollbackRuleAttribute( Type exceptionType )
		{
		    AssertUtils.ArgumentNotNull(exceptionType, "exceptionType");            
			if ( ! typeof(Exception).IsAssignableFrom( exceptionType ) )
			{
				throw new ArgumentException("Cannot construct rollback rule from " + exceptionType + "; " + "It's not an Exception");
			}
			_exceptionName = exceptionType.Name;
		}

		/// <summary>
		/// Returns the name of the exception.
		/// </summary>
		public string ExceptionName
		{
			get { return _exceptionName; }
		}

		/// <summary>
		/// Return the depth to the matching superclass execption <see cref="System.Type"/>.
		/// </summary>
		/// <remarks>
		/// A return value of 0 means that the <paramref name="exceptionType"/> matches. 
		/// </remarks>
		/// <param name="exceptionType">
		/// The <see cref="System.Type"/> of exception to find.
		/// </param>
		/// <returns>
		/// Return -1 if there's no match. Otherwise, return depth. Lowest depth wins.
		/// </returns>
		public int GetDepth( Type exceptionType )
		{
			return getDepth( exceptionType, 0 );
		}

		/// <summary>
		/// Return the depth to the matching superclass execption <see cref="System.Type"/>.
		/// </summary>
		/// <remarks>
		/// A return value of 0 means that the <paramref name="exception"/>s 
		/// <see cref="System.Type"/> matches. 
		/// </remarks>
		/// <param name="exception">
		/// The exception object to find.
		/// </param>
		/// <returns>
		/// Return -1 if there's no match. Otherwise, return depth. Lowest depth wins.
		/// </returns>
		public int GetDepth( object exception )
		{
			return GetDepth( exception.GetType() );
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> representation of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String"/> containing the exception covered by this instance.
		/// </returns>
		public override string ToString()
		{
			return "RollbackRule with pattern '" + ExceptionName + "'";
		}

		/// <summary>
		/// Override of <see cref="System.Object.GetHashCode"/>.
		/// </summary>
		/// <returns>The hashcode of the exception name covered by this instance.</returns>
		public override int GetHashCode()
		{
		    return base.GetHashCode();
		}

        
		/// <summary>
		/// Override of <see cref="System.Object.Equals(object)"/>.
		/// </summary>
		/// <param name="obj">The object to compare.</param>
		/// <returns><b>True</b> if the input object is equal to this instance.</returns>
		public override bool Equals(object obj)
		{
            if (ReferenceEquals(this, obj)) return true;
			RollbackRuleAttribute rollbackRuleAttribute = obj as RollbackRuleAttribute;
			if ( rollbackRuleAttribute == null )
			{
				return false;
			}
			return Equals( rollbackRuleAttribute );
		}

	    /// <summary>
		/// Strongly typed <c>Equals()</c> implementation.
		/// </summary>
		/// <param name="rollbackRuleAttribute">
		/// The <see cref="Spring.Transaction.Interceptor.RollbackRuleAttribute"/> to compare.
		/// </param>
		/// <returns>
		/// <b>True</b> if the input object is equal to the supplied <paramref name="rollbackRuleAttribute"/>.
		/// </returns>
		public bool Equals( RollbackRuleAttribute rollbackRuleAttribute )
		{
		    return base.Equals(rollbackRuleAttribute);
		}

		private int getDepth( Type exceptionType, int depth )
		{
			if ( ( exceptionType.Name.IndexOf( ExceptionName ) != -1 ) || ( exceptionType.FullName.IndexOf( ExceptionName ) != -1 ) )
			{
				return depth;
			}			
			if ( exceptionType == typeof(Exception))
			{
				return -1;
			}
			return getDepth( exceptionType.BaseType, depth + 1 );
		}
	}
}
