context: // pNAnt - A parallel .NET build tool
  // Copyright (C) 2001-2005 Gert Driesen
  //
  // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation; either version 2 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  //
  // Troy Laurin (fiontan@westnet.com.au)
  
  using NAnt.Core.Attributes;
  using NAnt.Core.Util;
  using System;
  using System.Text;
  
  namespace NAnt.Core.Tasks {
      /// <summary>
      /// Executes a set of tasks, and optionally catches a build exception to
      /// allow recovery or rollback steps to be taken, or to define some steps
      /// to be taken regardless if the tasks succeed or fail, or both.
      /// </summary>
      /// <remarks>
      ///   <para>
      ///   The tasks defined in the <c>&lt;<see cref="TryBlock" />&gt;</c> block
      ///   will be executed in turn, as they normally would in a target.
      ///   </para>
      ///   <para>
      ///   If a <c>&lt;<see cref="CatchBlock" />&gt;</c> block is defined, the 
      ///   tasks in that block will be executed in turn only if one of the tasks 
      ///   in the <c>&lt;<see cref="TryBlock" />&gt;</c> block fails. This 
      ///   failure will then be suppressed by the <c>&lt;<see cref="CatchBlock" />&gt;</c>
      ///   block.
      ///   </para>
      ///   <para>
      ///   The message associated with the failure can also be caught in a
      ///   property for use within the <c>&lt;<see cref="CatchBlock" />&gt;</c>
      ///   block.  The original contents of the property will be restored upon 
      ///   exiting the <c>&lt;<see cref="CatchBlock" />&gt;</c> block.
      ///   </para>
      ///   <para>
      ///   If a <c>&lt;<see cref="FinallyBlock" />&gt;</c> block is defined, the 
      ///   tasks in that block will be executed after the tasks in both the 
      ///   <c>&lt;<see cref="TryBlock" />&gt;</c> and <c>&lt;<see cref="CatchBlock" />&gt;</c>
      ///   blocks have been executed, regardless of whether any task fails in 
      ///   either block.
      ///   </para>
      /// </remarks>
      /// <example>
      ///   <code>
      ///     <![CDATA[
      /// <trycatch>
      ///     <try>
      ///         <echo message="In try" />
      ///         <fail message="Failing!" />
      ///     </try>
      ///     <catch>
      ///         <echo message="In catch" />
      ///     </catch>
      ///     <finally>
      ///         <echo message="Finally done" />
      ///     </finally>
      /// </trycatch>
      ///     ]]>
      ///   </code>
      ///   <para>
      ///   The output of this example will be:
      ///   </para>
      ///   <code>
      /// In try
      /// In catch
      /// Finally done
      ///   </code>
      ///   <para>
      ///   The failure in the <c>&lt;<see cref="TryBlock" />&gt;</c> block will 
      ///   not cause the build to fail.
      ///   </para>
      /// </example>
      /// <example>
      ///   <code>
      ///     <![CDATA[
      /// <trycatch>
      ///     <try>
      ///         <echo message="In try" />
      ///         <fail message="Just because..." />
      ///     </try>
      ///     <catch property="failure">
      ///         <echo message="Caught failure: ${failure}" />
      ///         <fail message="Bad catch" />
      ///     </catch>
      ///     <finally>
      ///         <echo message="Finally done" />
      ///     </finally>
      /// </trycatch>
      ///     ]]>
      ///   </code>
      ///   <para>
      ///   The output of this example will be:
      ///   </para>
      ///   <code>
      /// In try
      /// Caught failure: Just because...
      /// Finally done
      /// Build failed: Bad catch
      ///   </code>
      ///   <para>
      ///   Like the above, the failure in the <c>&lt;<see cref="TryBlock" />&gt;</c>
      ///   block does not cause the build to fail.  The failure in the 
      ///   <c>&lt;<see cref="CatchBlock" />&gt;</c> block does, however.
      ///   Note that the <c>&lt;<see cref="FinallyBlock" />&gt;</c> block is 
      ///   executed even though the <c>&lt;<see cref="CatchBlock" />&gt;</c>
      ///   block failed.
      ///   </para>
      /// </example>
      /// <example>
      ///   <code>
      ///     <![CDATA[
      /// <trycatch>
      ///     <try>
      ///         <echo message="In try" />
      ///         <fail message="yet again" />
      ///     </try>
      ///     <catch property="failure">
      ///         <echo message="Caught failure ${failure}" />
      ///         <fail message="Bad catch" />
      ///     </catch>
      ///     <finally>
      ///         <echo message="Finally done ${failure}" />
      ///     </finally>
      /// </trycatch>
      ///     ]]>
      ///   </code>
      ///   <para>
      ///   The output of this example will be:
      ///   </para>
      ///   <code>
      /// In try
      /// Caught failure yet again
      /// Build failed: Property 'failure' has not been set.
      ///   </code>
      ///   <para>
      ///   The <see cref="EchoTask" /> in the <c>&lt;<see cref="FinallyBlock" />&gt;</c>
      ///   block failed because the &quot;failure&quot; property was not defined 
      ///   after exiting the <c>&lt;<see cref="CatchBlock" />&gt;</c> block.  
      ///   Note that the failure in the <c>&lt;<see cref="FinallyBlock" />&gt;</c> 
      ///   block has eclipsed the failure in the <c>&lt;<see cref="CatchBlock" />&gt;</c>
      ///   block.
      ///   </para>
      /// </example>
      /// <example>
      ///   <code>
      ///     <![CDATA[
      /// <trycatch>
      ///     <try>
      ///         <property name="temp.file" value="${path::get-temp-file-name()}" />
      ///         <do-stuff to="${temp.file}" />
      ///         <fail message="Oops..." />
      ///     </try>
      ///     <finally>
      ///         <echo message="Cleaning up..." />
      ///         <if test="${property::exists('temp.file')}">
      ///             <delete file="${temp.file}" />
      ///         </if>
      ///     </finally>
      /// </trycatch>
      ///     ]]>
      ///   </code>
      ///   <para>
      ///   A more concrete example, that will always clean up the generated
      ///   temporary file after it has been created.
      ///   </para>
      /// </example>
      [TaskName("trycatch")]
      public class TryCatchTask : Task {
          private ElementContainer _tryBlock;
          private CatchElement _catchBlock;
          private ElementContainer _finallyBlock;
          /// <summary>
          /// The tasks in this block will be executed as a normal part of
          /// the build script.
          /// </summary>
          [BuildElement("try", Required=true)]
          public ElementContainer TryBlock {
              get { return _tryBlock; }
              set { _tryBlock = value; }
          }
  
          /// <summary>
          /// The tasks in this block will be executed if any task in the try
          /// block fails.
          /// </summary>
          [BuildElement("catch", Required=false)]
          public CatchElement CatchBlock {
              get { return _catchBlock; }
              set { _catchBlock = value; }
          }
  
          /// <summary>
          /// The tasks in this block will always be executed, regardless of
          /// what happens in the try and catch blocks.
          /// </summary>
          /// <remarks>
          /// Note that any failure in any of the tasks in this block will
          /// prevent any subsequent tasks from executing.
          /// </remarks>
          [BuildElement("finally", Required=false)]
          public ElementContainer FinallyBlock {
              get { return _finallyBlock; }
              set { _finallyBlock = value; }
          }
          /// <summary>
          /// Executes the task.
          /// </summary>
          protected override void ExecuteTask() {
              try {
                  if (TryBlock != null) {
                      TryBlock.Execute(this.CallStack);
                  }
              } catch (BuildException be) {
                  if (CatchBlock != null) {
                      CatchBlock.Catch(be, this.CallStack, this.PropertyAccessor);
                  } else {
                      throw;
                  }
              } finally {
                  if (FinallyBlock != null) {
                      FinallyBlock.Execute(this.CallStack);
                  }
              }
          }
          public class CatchElement : ElementContainer {
              private string _property;
              /// <summary>
              /// Defines the name of the property to save the message describing
              /// the failure that has been caught.
              /// </summary>
              /// <remarks>
              /// <para>
              /// The failure message is only available in the context of the catch
              /// block.  If you wish to preserve the message, you will need to save
              /// it into another property.
              /// </para>
              /// <para>
              /// Readonly properties cannot be overridden by this mechanism.
              /// </para>
              /// </remarks>
              [TaskAttribute("property", Required=false)]
              [StringValidator(AllowEmpty=false)]
              public string Property {
                  get { return _property; }
                  set { _property = StringUtils.ConvertEmptyToNull(value); }
              }
              public void Catch(BuildException be, TargetCallStack callStack, PropertyAccessor accessor) {
                  bool propertyExists = false;
                  string originalPropertyValue = null;
  
                  if (Property != null) {
                      propertyExists = accessor.Contains(Property);
  
                      if (accessor.Contains(Property))
                      {
                          originalPropertyValue = accessor[Property];
                      }
  
                      accessor[Property] = GetExceptionMessage(be);
                  }
  
                  try {
                      Execute(callStack);
                  } finally {
                      if (Property != null) {
                          if (!propertyExists) {
                              // if the property did not exist, then remove it again
                              accessor.Remove(Property);
                          } else {
                              // restore original value
                              if (!String.IsNullOrWhiteSpace(originalPropertyValue))
                              {
                                  accessor[Property] = originalPropertyValue;
                              }
                          }
                      }
                  }
              }
              
              /// <summary>
              /// Parses out the complete exception and inner exception information to be
              /// consumed by the catch element.
              /// </summary>
              /// <param name="e">
              /// The exception object to get the messages from.
              /// </param>
              /// <returns>
              /// The complete exception message.
              /// </returns>
              private string GetExceptionMessage(Exception e)
              {
  
gt:                 StringBuilder sb = new StringBuilder();