Subversion Repositories DevTools

Rev

Rev 1320 | Blame | Last modification | View Log | RSS feed

package com.erggroup.buildtool.ripple;

import java.io.File;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.Vector;

import org.apache.log4j.Logger;

import com.erggroup.buildtool.smtp.Smtpsend;

public class Package
{
  /**name of package, must not contain spaces
   * @attribute
   */
  String mName = new String();

  /**package scope
   * @attribute
   */
  String mExtension = new String();

  /**instance identifier
   * @attribute
   */
  String mVersion = new String();

  /**unique identifier
   * for daemon builds = mName + mExtension
   * for escrow builds = mName + mVersion + mExtension
   * @attribute
   */
  String mAlias = new String();

  /**Version Control System Tag
   * @attribute
   */
  String mVcsTag = new String();

  /**build standards
   * @attribute
   */
  Vector<BuildStandard> mBuildStandardCollection = new Vector<BuildStandard>();

  /**GBE_MACHTYPE used to build generic packages for this baseline
   * only has meaning in the daemon build, not the escrow build
   * accessed by BuildStandard::getPlatform, getBuildStandard
   * @attribute
   */
  public static final String mGenericMachtype = System.getenv("GBE_MACHTYPE");

  /**build dependencies by package alias
   * @attribute
   */
  Vector<String> mDependencyCollection = new Vector<String>();

  /**primary package version key pv_id in database
   * @attribute
   */
  int mId;

  /**indication of the nature of change
   * @attribute
   */
  Package.VersionNumberingStandard mChangeType = new VersionNumberingStandard();

  /**determines what field is rippled on a package version whose dependencies have changed
   * @attribute
   */
  Package.VersionNumberingStandard mRippleField = new VersionNumberingStandard();

  /**interested owners
   * @attribute
   */
  private Vector<String> mBuildFailureEmailCollection = new Vector<String>();

  /**when true will trigger unit tests as part of the package build phase in daemon mode
   * @attribute
   */
  boolean mHasAutomatedUnitTests = false;

  /**when true, do not ripple this package through packages which are dependent upon it in daemon mode
   * @attribute
   */
  boolean mAdvisoryRipple = false;

  /**determines the build file the package is built in, or not
   *  1 buildfile 1 etc
   *  0 not yet processed (initial value)
   * -1 not reproducible
   * -2 not reproducible on the build platforms configured for this release
   * -3 do not ripple
   * -4 directly dependent on package versions not in the baseline
   * -5 indirectly dependent on package versions which are not reproducible
   *    because of -1, -2 (escrow), -3 (daemon), -4, -6
   * -6 circular dependency
   * @attribute
   */
  int mBuildFile = 0;

  /**build dependencies by package
   * @attribute
   */
  Vector<Package> mPackageDependencyCollection = new Vector<Package>();

  /**used for escrow build purposes
   * set true when a package has been processed
   * @attribute
   */
  boolean mProcessed = false;

  /**set true for WIP package versions
   * only used in daemon mode
   * @attribute
   */
  boolean mDirectlyPlanned = false;

  /**set true when it is determined to be ripple built
   * @attribute
   */
  boolean mIndirectlyPlanned = false;

  /**non zero instruction number when it is determined to be ripple built by force
   * @attribute
   */
  int mForcedRippleInstruction = 0;

  /**non zero instruction number when it is determined to be test built
   * @attribute
   */
  int mTestBuildInstruction = 0;

  /**test build email destination
   * @attribute
   */
  String mTestBuildEmail;

  /**Version Control System Tag
   * @attribute
   */
  String mTestBuildVcsTag = new String();

  /**build standards
   * @attribute
   */
  Vector<BuildStandard> mTestBuildStandardCollection = new Vector<BuildStandard>();

  /**build dependencies by package alias
   * @attribute
   */
  Vector<String> mTestBuildDependencyCollection = new Vector<String>();

  /**build dependencies by pv_id (-1 or not used for planned dependencies)
   * @attribute
   */
  Vector<Integer> mDependencyIDCollection = new Vector<Integer>();

  /**unique pkg_id in the database
   * used for querying package version existence in the database in daemon mode
   * @attribute
   */
  int mPid;

  /**maximum major number supported for determining ripple number
   * @attribute
   */
  int mMajorLimit;

  /**maximum minor number supported for determining ripple number
   * @attribute
   */
  int mMinorLimit;

  /**maximum patch number supported for determining ripple number
   * @attribute
   */
  int mPatchLimit;

  /**maximum build number number supported for determining ripple number
   * @attribute
   */
  int mBuildLimit;

  /**Logger
   * @attribute
   */
  private static final Logger mLogger = Logger.getLogger(Package.class);

  /**dpkg archive location
   * @attribute
   */
  public static final String mGbeDpkg = System.getenv("GBE_DPKG");

  /**Exception message used upon detection an archive does not exist
   * Seems this is a rare but transient and recoverable scenario
   * @attribute
   */
  public static final String mRecoverable = "dpkg_archive does not exist, recovery will be attempted";

  /**true if the package exists in the package archive (dpkg_archive)
   * @attribute
   */
  private boolean mArchivalExistence = true;

  /**when true will trigger source control interaction eg labelling
   * @attribute
   */
  public boolean mRequiresSourceControlInteraction = true;

  /**when true has been checked for circular dependency
   * @attribute
   */
  boolean mCheckedCircularDependency = false;

  /**when true has circular dependency
   * @attribute
   */
  boolean mHasCircularDependency = false;

  /**constructor
   */
  Package(int pv_id, String pkg_name, String v_ext, String alias, 
          String pkg_vcs_tag, char change_type, char ripple_field)
  {
    mLogger.debug("Package 1: pv_id " + pv_id + " pkg_name " + pkg_name + " v_ext " + v_ext + " alias " + alias + " pkg_vcs_tag " + pkg_vcs_tag + " change_type " + change_type);
    mId = pv_id;
    mName = pkg_name;
    mVersion = "0.0.0000";
    mExtension = v_ext;
    mAlias = alias;
    mVcsTag = pkg_vcs_tag;

    if (change_type == 'M')
    {
      // a ripple_field of 'L' indicates this package has limited version numbering
      mChangeType.setMajor(ripple_field == 'L' ? true : false);
    }
    else if (change_type == 'N')
    {
      mChangeType.setMinor(ripple_field == 'L' ? true : false);
    }
    else
    {
      mChangeType.setPatch(ripple_field == 'L' ? true : false);
    }
  }

  /**constructor
   */
  Package(int pv_id, String pkg_name, String pkg_version, String v_ext, 
          String alias, String pkg_vcs_tag, char ripple_field)
  {
    mLogger.debug("Package 2: pv_id " + pv_id + " pkg_name " + pkg_name + " pkg_version " + pkg_version + " v_ext " + v_ext + " alias " + alias + " pkg_vcs_tag " + pkg_vcs_tag + " ripple_field " + ripple_field);
    mId = pv_id;
    mName = pkg_name;
    mVersion = pkg_version;
    int endindex = mVersion.length() - v_ext.length();
    
    if ( endindex > 0 )
    {
      mVersion = mVersion.substring(0, endindex);
    }
    
    mExtension = v_ext;
    mAlias = alias;
    mVcsTag = pkg_vcs_tag;

    // setBuild is the default
    if (ripple_field == 'M')
    {
      mRippleField.setMajor();
    }
    else if (ripple_field == 'm')
    {
      mRippleField.setMinor();
    }
    else if (ripple_field == 'p')
    {
      mRippleField.setPatch();
    }
    else if (ripple_field == 'L')
    {
      mRippleField.setLimit();
    }
  }

  /**constructor
   */
  Package()
  {
    mLogger.debug("Package 3");
    mId = 0;
    mName = "null";
    mExtension = "null";
    mAlias = "null";
    mVcsTag = "null";
  }

  /**constructor for test build purposes
   */
  Package(String pkg_name, String v_ext, String alias, 
          String pkg_vcs_tag, int testBuildInstruction, String email)
  {
    mLogger.debug("Package 4: pkg_name " + pkg_name + " v_ext " + v_ext + " alias " + alias + " pkg_vcs_tag " + pkg_vcs_tag );
    // don't need pv_id
    mId = -1;
    mName = pkg_name;
    // avoid interaction with real versions
    mVersion = "0.0.0000";
    mExtension = v_ext;
    mAlias = alias;
    mTestBuildInstruction = testBuildInstruction;
    mTestBuildEmail = email;
    mTestBuildVcsTag = pkg_vcs_tag;

  }
  
/**constructor for unit test purposes
  */
  public Package(ReleaseManager rm, String version, int majorLimit, int minorLimit, int patchLimit, int buildNumberLimit)
  {
    mId = -1;
    mRippleField.setLimit();
    mVersion = version;
    mMajorLimit = majorLimit;
    mMinorLimit = minorLimit;
    mPatchLimit = patchLimit;
    mBuildLimit = buildNumberLimit;

    if ( version.endsWith(".cots") )
    {
        mExtension = ".cots";
        mVersion = version.substring( 0, version.length() - 5 );
        mChangeType.setMajor(false);
        mChangeType.setMinor(false);
        mChangeType.setPatch(true);
        mRippleField.setBuild();
    }

    try
    {
      mId = applyPV( rm, 0 );
    }
    catch(Exception e)
    {
    }
  }

  /**accessor for unit test purposes
   */
  public int getId()
  {
    return mId;
  }
  
  /**accessor for unit test purposes
   */
  public String getVersion()
  {
    return mVersion;
  }
  
  /**returns true if mBuildStandardCollection is not empty
   */
  boolean isReproducible()
  {
    mLogger.debug("isReproducible on Package " + mName);
    boolean retVal = false;
    
    if ( mBuildStandardCollection.size() > 0 )
    {
      retVal = true;
    }
    
    mLogger.info("isReproducible returned " + retVal);
    return retVal;
  }

  /**returns true if at least one of its BuildStandards has mWin32 or mGeneric true
   */
  boolean isWin32Built()
  {
    mLogger.debug("isWin32Built on Package " + mName);
    boolean retVal = false;
    for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard buildStandard = it.next();

      if (buildStandard.getWin32() || buildStandard.getGeneric())
      {
        retVal = true;
        break;
      }
    }

    mLogger.info("isWin32Built returned " + retVal);
    return retVal;
  }

  /**returns true if at least one of its BuildStandards has mSolaris or mGeneric true
   */
  boolean isSolarisBuilt()
  {
    mLogger.debug("isSolarisBuilt on Package " + mName);
    boolean retVal = false;
    for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard buildStandard = it.next();

      if (buildStandard.getSolaris() || buildStandard.getGeneric())
      {
        retVal = true;
        break;
      }
    }

    mLogger.info("isSolarisBuilt returned " + retVal);
    return retVal;
  }

  /**returns true if at least one of its BuildStandards has mLinux or mGeneric true
   */
  boolean isLinuxBuilt()
  {
    mLogger.debug("isLinuxBuilt on Package " + mName);
    boolean retVal = false;
    for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard buildStandard = it.next();

      if (buildStandard.getLinux() || buildStandard.getGeneric())
      {
        retVal = true;
        break;
      }
    }

    mLogger.info("isLinuxBuilt returned " + retVal);
    return retVal;
  }

  /**returns true if at least one of its BuildStandards has mGeneric true
   */
  boolean isGeneric()
  {
    mLogger.debug("isGeneric on Package " + mName);
    boolean retVal = false;
    for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard buildStandard = it.next();

      if (buildStandard.getGeneric())
      {
        retVal = true;
        break;
      }
    }

    mLogger.info("isGeneric returned " + retVal);
    return retVal;
  }

  /**applies the required version number change
   * returns 0 on success
   *         1 on cannot work with non standard versioning
   *         2 on ripple field limitations prevent a ripple build
   */
  int applyPV(ReleaseManager releaseManager, int rtag_id) throws Exception
  {
    mLogger.debug("applyPV on Package " + mName);
    // four scenarios, only applyPV for 3 of them
    // WIP/test build exists:           mDirectlyPlanned == true;   mIndirectlyPlanned == true; mArchivalExistence don't care; mForcedRipple don't care - applyPV
    // Package version is out of date:  mDirectlyPlanned == false;  mIndirectlyPlanned == true; mArchivalExistence == true;    mForcedRipple don't care - applyPV
    // Forced ripple:                   mDirectlyPlanned == false;  mIndirectlyPlanned == true; mArchivalExistence don't care; mForcedRipple > 0        - applyPV
    // Package version does not exist:  mDirectlyPlanned == false;  mIndirectlyPlanned == true; mArchivalExistence == false;   mForcedRipple = 0    - do not applyPV
    if ( !mDirectlyPlanned && mIndirectlyPlanned && !mArchivalExistence && mForcedRippleInstruction == 0 )
    {
      // the package has an mIndirectlyPlanned flag set true in daemon mode because the package does not exist in an archive
      // do not apply a different package version
      mLogger.info("applyPV !mDirectlyPlanned && mIndirectlyPlanned && !mArchivalExistence && zero mForcedRippleInstruction on Package " + mName);
      releaseManager.claimVersion(mPid, mVersion + mExtension, rtag_id);
      mLogger.info("applyPv returned 0");
      return 0;
    }

    // override - no longer doing a rebuild - version number change from this point on
    if ( mTestBuildInstruction == 0 )
    {
      mRequiresSourceControlInteraction = true;
    }
    
    MutableInt major = new MutableInt();
    major.value = 0;
    MutableInt minor = new MutableInt();
    minor.value = 0;
    MutableInt patch = new MutableInt();
    patch.value = 1000;

    String field[] = mVersion.split("\\D");
    String nonStandardCotsVersion = "";

    if ( field.length == 3 )
    {
      major.value = Integer.parseInt(field[0]);
      minor.value = Integer.parseInt(field[1]);
      patch.value = Integer.parseInt(field[2]);
    }
    else
    {
      //
      //    Can ripple a .cots package under very controlled conditions
      //    Its ends with a .patchBuild field
      //    Package is marked as riple via build number
      //    Chaneg type og Major and Minor are not allowed
      //
      if ( !mChangeType.mMajor &&
           !mChangeType.mMinor &&
           mRippleField.mBuild &&
           mExtension.compareTo(".cots") == 0 &&
           field.length > 0 )
      {
        // DEVI 52782
        // allow and work with (ripple build) versions a.b.c.d....xxxx
        // where xxxx.length > 3
        String patchStr = field[field.length - 1];
        int patchLen = patchStr.length();

        // check patchStr is the last (at least 4) digits
        if ( patchLen > 3 && mVersion.substring( mVersion.length() - patchLen, mVersion.length() ).compareTo(patchStr) == 0 )
        {
          patch.value = Integer.parseInt(patchStr);
          nonStandardCotsVersion = mVersion.substring(0, mVersion.length() - patchLen );
        }
      }
      
      if ( nonStandardCotsVersion.length() == 0 )
      {
        // cannot work with non standard versioning
        mLogger.error("applyPV cannot work with non standard versioning");
        mLogger.info("applyPv returned 1");
        return 1;
      }
    }

    if ( nonStandardCotsVersion.length() == 0 &&
         patch.value < 1000 && 
         field[2].substring(0, 1).compareTo("0") != 0 )
    {
      mLogger.info("applyPV accomodate old style mVersion of the form 1.0.1");
      patch.value = patch.value * 1000;
    }
      
    // mChangeType overrides mRippleField
    do
    {
      if ( mChangeType.mMajor )
      {
        if ( !incrementFieldsAccordingToLimits(4, major, minor, patch) )
        {
          mLogger.info("applyPv returned 2");
          return 2;
        }
      }
      else if ( mChangeType.mMinor )
      {
        if ( !incrementFieldsAccordingToLimits(3, major, minor, patch) )
        {
          mLogger.info("applyPv returned 2");
          return 2;
        }
      }
      else if ( mChangeType.mPatch )
      {
        if ( !incrementFieldsAccordingToLimits(2, major, minor, patch) )
        {
          mLogger.info("applyPv returned 2");
          return 2;
        }
      }
      else
      {
        if ( mRippleField.mMajor )
        {
          major.value++;
          mLogger.info("applyPV mRippleField.mMajor " + major.value);
          minor.value = 0;
          patch.value = 0;
        }
        else if ( mRippleField.mMinor )
        {
          minor.value++;
          mLogger.info("applyPV mRippleField.mMinor " + minor.value);
          patch.value = 0;
        }
        else if ( mRippleField.mPatch )
        {
          do
          {
            patch.value++;
          } while ( ( patch.value / 1000 ) * 1000 != patch.value );
          mLogger.info("applyPV mRippleField.mPatch " + patch.value);
        }
        else if ( mRippleField.mBuild )
        {
          patch.value++;
          mLogger.info("applyPV mRippleField.mBuild " + patch.value);
        }
        else
        {
          if ( !incrementFieldsAccordingToLimits(1, major, minor, patch) )
          {
            mLogger.info("applyPv returned 2");
            return 2;
          }
        }
      }

      if ( nonStandardCotsVersion.length() == 0 )
      {
        mVersion = String.valueOf(major.value) + "." + String.valueOf(minor.value) + ".";
      }
      else
      {
        mVersion = nonStandardCotsVersion;
      }
      
      if ( patch.value < 10 )
      {
        mVersion += "000";
      }
      else if ( patch.value < 100 )
      {
        mVersion += "00";
      }
      else if ( patch.value < 1000 )
      {
        mVersion += "0";
      }
      
      mVersion += String.valueOf(patch.value);
    } while ( exists(releaseManager, rtag_id) );
    
    releaseManager.claimVersion(mPid, mVersion + mExtension, rtag_id);
    mLogger.info("applyPv returned 0");
    return 0;
  }

  /**increments fields according to mRippleField.mLimit if necessary
   * will apply it to the field passed as follows
   * 1 = build
   * 2 = patch
   * 3 = minor
   * other = major
   * returns true on success
   *         false on ripple field limitations prevent a ripple build
   */
  private boolean incrementFieldsAccordingToLimits(int field, MutableInt major, MutableInt minor, MutableInt patch)
  {
    boolean retVal = true;
    
    if (!mChangeType.mLimit && !mRippleField.mLimit)
    {
      // simple case
      // no need to take field limits into consideration
      switch (field)
      {
      case 1:
        // unreachable
        // the only scenario involving build number manipulation involves the mRippleField.mLimit being set
        retVal = false;
        break;
      case 2:
        do
        {
          patch.value++;
        } while ( ( patch.value / 1000 ) * 1000 != patch.value );
        mLogger.info("incrementFieldsAccordingToLimits patch " + patch.value);
        break;
      case 3:
        minor.value++;
        mLogger.info("incrementFieldsAccordingToLimits minor " + minor.value);
        patch.value = 0;
        break;
      default:
        major.value++;
        mLogger.info("incrementFieldsAccordingToLimits major " + major.value);
        minor.value = 0;
        patch.value = 0;
      }
    }
    else
    {
      // take field limits into consideration
      boolean changeOccurred = false;
      boolean incrementField = true;
      
      switch (field)
      {
      case 1:
        if ( mBuildLimit != 0 )
        {
          // increment or reset the patch build number
          int buildNumber = patch.value - (patch.value/1000) * 1000;
          
          if ( buildNumber < mBuildLimit )
          {
            // can increment the patch build number
            patch.value++;
            mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit build number " + patch.value);
            changeOccurred = true;
            incrementField = false;
          }
          else
          {
            if ( mPatchLimit == 0 )
            {
              // reset the patch number and patch build number
              patch.value = 0;
            }
          }
        }
        // no break by design
      case 2:
        if ( mPatchLimit != 0 && incrementField )
        {
          // increment or reset the patch number
          if ( ( patch.value / 1000 ) < mPatchLimit )
          {
            do
            {
              patch.value++;
            } while ( ( patch.value / 1000 ) * 1000 != patch.value );
            
            mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit patch " + patch.value);
            changeOccurred = true;
            incrementField = false;
          }
          else
          {
            // reset the patch number and patch build number
            patch.value = 0;
          }
        }
        // no break by design
      case 3:
        if ( mMinorLimit != 0 && incrementField )
        {
          // increment or reset the minor number
          if ( minor.value < mMinorLimit )
          {
            minor.value++;
            patch.value = 0;
            mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit minor " + minor.value);
            changeOccurred = true;
            incrementField = false;
          }
          else
          {
            // reset the minor number
            minor.value = 0;
          }
        }
        // no break by design
      default:
        if ( mMajorLimit != 0 && incrementField )
        {
          // increment or reset the major number
          if ( major.value < mMajorLimit )
          {
            // increment the major number
            changeOccurred = true;
            major.value++;
            minor.value = 0;
            patch.value = 0;
            mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit major " + major.value);
          }
        }
      }
      
      if ( !changeOccurred )
      {
        // unable to increment a field due to field limitations
        mLogger.error("incrementFieldsAccordingToLimits ripple field limitations prevent a ripple build");
        mLogger.info("incrementFieldsAccordingToLimits returned false");
        retVal = false;
      }
    }
    
    return retVal;
  }
  
  /**returns true if the version exists in the dpkg_archive or release manager database
   * claims the version in the release manager database
   */
  private boolean exists(ReleaseManager releaseManager, int rtag_id) throws Exception
  {
    mLogger.debug("exists 1 on Package " + mName + " version " + mVersion + " extension " + mExtension);
    boolean retVal = false;
    
    if ( !ReleaseManager.mUseDatabase )
    {
      mLogger.info("exists 1 !releaseManager.mUseDatabase");
    }
    else
    {
      retVal = exists();

      if ( !retVal )
      {
        String pkg_version = new String(mVersion);
        
        if ( mExtension.length() > 0 )
        {
          pkg_version += mExtension;
        }
        
        retVal = releaseManager.queryPackageVersions(mPid, pkg_version);
      }
    }
    
    mLogger.info("exists 1 returned " + retVal);
    return retVal;
  }

  /**returns true if the required package archives (dpkg_archive) exist
   * attempt to recover from their transient loss 
   */
  public static boolean recover()
  {
    mLogger.debug("recover");
    boolean retVal = false;

    String Release = mGbeDpkg;
    if (Release != null )
    {
      File dpkg = new File(mGbeDpkg);
      
      if ( dpkg.exists() )
      {
        retVal = true;
        mLogger.fatal("recover: dpkg_archive access has been restored");
      }
    }
    
    mLogger.info("recover returned " + retVal);
    return retVal;
  }
  
  /**returns true if the version exists in a package archive (dpkg_archive)
   */
  boolean exists()
    throws Exception
  {
    mLogger.debug("exists 2 on Package " + mName);
    boolean retVal = false;

    String Release = mGbeDpkg;
    if (Release == null)
    {
      mLogger.fatal("exists 2 Release == null");
      throw new Exception("exists 2 Release == null");
    }
    
    File dpkg = new File(mGbeDpkg);
    if ( !dpkg.exists()  )
    {
      mLogger.fatal("exists 2 " + mRecoverable);
      throw new Exception(mRecoverable);
    }

    String fs = System.getProperty( "file.separator" );
    String name = new String(Release);
    name += fs + mName + fs + mVersion + mExtension;
    File release = new File(name);

    if (release.exists())
    {
      mLogger.info("exists 2 release.exists()");
      retVal = true;
    }

    mArchivalExistence = retVal;
    mLogger.info("exists 2 returned " + retVal);
    return retVal;
  }
  
  /**returns email information
   */
  String emailInfo( String lf )
  {
    String retVal = new String();
    
    for (Iterator<String> it = mBuildFailureEmailCollection.iterator(); it.hasNext(); )
    {
      String email = it.next();
      retVal +=
      "  <owner email=\"" + email +"\"/>" + lf;
    }
    
    return retVal;
  }

  /**returns email information
   */
  String emailInfoNonAntTask()
  {
    String retVal = null;
    
    for (Iterator<String> it = mBuildFailureEmailCollection.iterator(); it.hasNext(); )
    {
      String email = it.next();
      
      if ( retVal == null )
      {
        retVal = new String();
      }
      else
      {
        retVal += ",";
      }
      retVal += email;
    }
    
    return retVal;
  }

  /**adds email to mBuildFailureEmailCollection if unique
   */
  void addEmail( String email )
  {
    boolean alreadyExists = false;
    
    for (Iterator<String> it = mBuildFailureEmailCollection.iterator(); it.hasNext(); )
    {
      String existingEmail = it.next();
      
      if ( existingEmail.compareTo(email) == 0 )
      {
        alreadyExists = true;
        break;
      }
    }
    
    if ( !alreadyExists )
    {
      mBuildFailureEmailCollection.add(email);
    }
  }

  /**accessor method
   */
  void setEmail()
  {
    mBuildFailureEmailCollection.clear();
    addEmail( mTestBuildEmail );
  }

  /**accessor method
   */
  void setDependencyCollection()
  {
    // does not worry about mPackageDendencyCollection by design
    mDependencyCollection.clear();
    
    for (Iterator<String> it = mTestBuildDependencyCollection.iterator(); it.hasNext(); )
    {
      String dependency = it.next();
      mDependencyCollection.add(dependency);
    }
  }

  /**accessor method
   */
  void setBuildStandardCollection()
  {
    mBuildStandardCollection.clear();
    
    for (Iterator<BuildStandard> it = mTestBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard buildStandard = it.next();
      mBuildStandardCollection.add(buildStandard);
    }
  }

  /**sends email notification and marks the instruction complete in the database
   */
  public void completeTestBuild( String mailServer, String mailSender, ReleaseManager releaseManager, String release, boolean success ) throws SQLException, Exception
  {
    mLogger.debug("completeTestBuild");
    
    if ( mTestBuildInstruction == 0)
    {
      mLogger.fatal("completeTestBuild. Not Build Instruction");
      return;
    }
    String indentString = "&nbsp;&nbsp;&nbsp;&nbsp;";
    String mailBody="Test build issues are identified in preceding build failure email.<p>" +
                    "Release: " + release + "<br>" +
                    "Package: " + mAlias + "<br>" +
                    "VcsTag: " + mVcsTag + "<br>" +
                    "Build dependencies:<br>";
    
    for (Iterator<Package> it3 = mPackageDependencyCollection.iterator(); it3.hasNext(); )
    {
      Package depend = it3.next();
      
      String dependsExtension = depend.mExtension;
      String dependsVersion = depend.mVersion;
      
      if ( dependsExtension.length() > 0 )
      {
        dependsVersion += dependsExtension;
      }
      mailBody += indentString + "\'" + depend.mName + "\',\'" + dependsVersion + "\' <br>";
    }

    mailBody += "<br>Build standards:<br>";

    for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext(); )
    {
      BuildStandard bs = it.next();
      
      String platform = bs.getPlatform(!ReleaseManager.mUseDatabase, false);
    
      if ( platform.length() > 0 )
      {
        mailBody += indentString + platform + ", ";
      }

      String standard = bs.getBuildStandard(!ReleaseManager.mUseDatabase, false);
      
      if ( standard.length() > 0 )
      {
        mailBody += standard + "<br>";
      }
    }

    mailBody += "<p><hr>";
    try
    {
      String target = emailInfoNonAntTask();
mLogger.fatal("completeTestBuildEmail Server: " + mailServer);
mLogger.fatal("completeTestBuildEmail Sender: " + mailSender);
mLogger.fatal("completeTestBuildEmail Target: " + target);
      
      Smtpsend.send(
      mailServer,           // mailServer
      mailSender,           // source
      target,               // target
      mailSender,           // cc
      null,                 // bcc
      success == true ? "TEST BUILD COMPLETED SUCCESSFULLY" : "TEST BUILD FAILED", // subject
      mailBody,             // body
      null                  // attachment
      );
    }
    catch( Exception e )
    {
        mLogger.warn("Email Failure: completeTestBuild:" + e.getMessage());
    }
    
    releaseManager.markDaemonInstCompletedConnect(mTestBuildInstruction);
mLogger.fatal("completeTest. Returning");
  }
  
  /**returns true if the package has a circular dependency
   */
  public boolean hasCircularDependency(RippleEngine ripEng)
  {
    mLogger.debug("hasCircularDependency");
    boolean retVal = detectCircularDependency( mAlias, ripEng );
    mLogger.info("hasCircularDependency returned " + retVal);
    return retVal;
  }
  
  /**returns true if the package has a circular dependency
   */
  public boolean detectCircularDependency(String alias, RippleEngine ripEng)
  {
    mLogger.debug("detectCircularDependency");
    boolean retVal = false;
    
    // if this package has yet to be checked for circular dependency
    if ( !mCheckedCircularDependency )
    {
      for (Iterator<String> it2 = mDependencyCollection.iterator(); it2.hasNext(); )
      {
        String dependencyAlias = it2.next();
        
        // check its direct dependencies for an alias match
        if ( alias.compareTo( dependencyAlias ) == 0 )
        {
          retVal = true;
          break;
        }
        
        Package dependency = ripEng.findPackage( dependencyAlias );
        
        // check its indirect dependencies for an alias match
        if ( dependency.detectCircularDependency(alias, ripEng) )
        {
          retVal = true;
          break;
        }
      }
      // mark the check complete
      mCheckedCircularDependency = true;
      // persist the circular dependency outcome
      mHasCircularDependency = retVal;
    }
    else
    {
      // return the persisted circular dependency outcome
      retVal = mHasCircularDependency;
    }
    mLogger.info("detectCircularDependency 2 returned " + retVal);
    return retVal;
  }
  
  /**entity class supporting the ERG version numbering standard:
   * <major>.<minor>.<patch/build>
   * patch/build is at least a 4 digit number whose last 3 digits represent the build
   */
  public class VersionNumberingStandard
  {
    /**in terms of the mChangeType Package field,
     * when true indicates the contract of the package has changed in a non backwardly compatible manner
     * in terms of the mRippleField Package field,
     * when true indicates the major version number will be incremented
     * @attribute
     */
    private boolean mMajor = false;

    /**in terms of the mChangeType Package field,
     * when true indicates the contract of the package has changed in a backwardly compatible manner
     * in terms of the mRippleField Package field,
     * when true indicates the minor version number will be incremented
     * @attribute
     */
    private boolean mMinor = false;

    /**in terms of the mChangeType Package field,
     * when true indicates the contract of the package has not changed, but the package has changed internally
     * in terms of the mRippleField Package field,
     * when true indicates the minor version number will be incremented
     * @attribute
     */
    private boolean mPatch = false;

    /**in terms of the mChangeType Package field,
     * when true indicates the package has not changed, its dependencies potentially have
     * in terms of the mRippleField Package field,
     * when true indicates the build number will be incremented
     * @attribute
     */
    private boolean mBuild = true;

    /**in terms of the mChangeType Package field,
     * when true indicates the major, minor, and patch number will be incremented according to field limits
     * in terms of the mRippleField Package field,
     * when true indicates the major, minor, patch and build number will be incremented according to field limits
     * @attribute
     */
    private boolean mLimit = false;

    /**constructor
     */
    private VersionNumberingStandard()
    {
      mLogger.debug("VersionNumberingStandard");
    }

    /**sets mBuild true, mMajor false, mMinor false, mPatch false, mLimit false
     */
    void setBuild()
    {
      mLogger.debug("setBuild");
      mBuild = true;
      mMajor = false;
      mMinor = false;
      mPatch = false;
      mLimit = false;
    }

    /**sets mBuild false, mMajor true, mMinor false, mPatch false, mLimit false
     */
    void setMajor()
    {
      mLogger.debug("setMajor");
      mBuild = false;
      mMajor = true;
      mMinor = false;
      mPatch = false;
      mLimit = false;
    }

    /**sets mBuild false, mMajor true, mMinor false, mPatch false, mLimit limit
     */
    void setMajor( boolean limit )
    {
      mLogger.debug("setMajor " + limit);
      mBuild = false;
      mMajor = true;
      mMinor = false;
      mPatch = false;
      mLimit = limit;
    }

    /**sets mBuild false, mMajor false, mMinor true, mPatch false, mLimit false
     */
    void setMinor()
    {
      mLogger.debug("setMinor");
      mBuild = false;
      mMajor = false;
      mMinor = true;
      mPatch = false;
      mLimit = false;
    }

    /**sets mBuild false, mMajor false, mMinor true, mPatch false, mLimit limit
     */
    void setMinor( boolean limit )
    {
      mLogger.debug("setMinor " + limit);
      mBuild = false;
      mMajor = false;
      mMinor = true;
      mPatch = false;
      mLimit = limit;
    }

    /**sets mBuild false, mMajor false, mMinor false, mPatch true, mLimit false
     */
    void setPatch()
    {
      mLogger.debug("setPatch");
      mBuild = false;
      mMajor = false;
      mMinor = false;
      mPatch = true;
      mLimit = false;
    }

    /**sets mBuild false, mMajor false, mMinor false, mPatch true, mLimit limit
     */
    void setPatch( boolean limit )
    {
      mLogger.debug("setPatch");
      mBuild = false;
      mMajor = false;
      mMinor = false;
      mPatch = true;
      mLimit = limit;
    }

    /**sets mBuild false, mMajor false, mMinor false, mPatch false, mLimit true
     */
    void setLimit()
    {
      mLogger.debug("setPatch");
      mBuild = false;
      mMajor = false;
      mMinor = false;
      mPatch = false;
      mLimit = true;
    }

  }

}