Subversion Repositories DevTools

Rev

Rev 7494 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.erggroup.buildtool.ripple;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.erggroup.buildtool.ripple.BuildFile.BuildFileState;
import com.erggroup.buildtool.ripple.Package.PkgDependency;
import com.erggroup.buildtool.ripple.ReleaseManager.BuildReason;
import com.erggroup.buildtool.smtp.CreateUrls;
import com.erggroup.buildtool.smtp.Smtpsend;
import com.erggroup.buildtool.utilities.StringAppender;
import com.erggroup.buildtool.utilities.XmlBuilder;

/**Plans release impact by generating a set of Strings containing build file content.
 */
public class RippleEngine
{

    /**configured mail server
     * @attribute
     */
    private String mMailServer = "";

    /**configured mail sender user
     * @attribute
     */
    private String mMailSender = "";

    /**configured global email target
     * @attribute
     */
    private String mMailGlobalTarget = "";

    /** Vector of email addresses for global and project wide use
     *  Most emails will be sent to this list of email recipients
     */
    public List<String> mMailGlobalCollection = new ArrayList<String>();

    /**name associated with the baseline
     * @attribute
     */
    public String mBaselineName = "";
    
    /** Data to control the build plan
     * 
     */
    public PlanControl mPlanControl = new PlanControl();

    /**Collection of build exceptions associated with the baseline
     * Used to determine (and report) what change in build exceptions happens as part of planRelease
     * Daemon centric
     * @aggregation shared
     * @attribute
     */
    ArrayList<BuildExclusion> mBuildExclusionCollection = new ArrayList<BuildExclusion>();

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

    /** Escrow information - commands to set up escrow
     * @attribute
     */
    private String mEscrowSetup;

    /** Escrow information - Raw data (May not be used)
     * @attribute
     */
    private String mEscrowRawData;

    /** Collections of packages
     */
    private PackageCollection mPackageCollection = new PackageCollection();
    private PackageCollection mPackageCollectionWip = new PackageCollection();
    private PackageCollection mPackageCollectionTest = new PackageCollection();
    private PackageCollection mPackageCollectionRipple = new PackageCollection();
    private PackageCollection mPackageCollectionAll = new PackageCollection();

    /**index to current String item
     * @attribute
     */
    private int mBuildIndex;

    /**Database abstraction
     * @attribute
     */
    ReleaseManager mReleaseManager;

    /**Baseline identifier (rtag_id for a release manager baseline, bom_id for deployment manager baseline)
     * @attribute
     */
    private int mBaseline;

    /** Escrow Only: SBOM_ID
     */
    private int mSbomId;

    /** RTAG_ID
     *  Set from mBaseline
     */
    private int mRtagId;

    /**When true, mBuildCollection contains one item based on a release manager rtag_id and contains a daemon property
     * When false, mBuildCollection contains at least one item based on a deployment manager bom_id
     * Will be accessed by the Package class to calculate its mAlias
     * @attribute
     */
    public boolean mDaemon;

    /**collection of build file content in String form
     * @attribute
     */
    private ArrayList<BuildFile> mBuildCollection = new ArrayList<BuildFile>();

    /** List of packages that we plan to build
     * Used to provide feedback into RM
     * Only the first entry is about to be built as we re-plan every cycle
     */
    private ArrayList<PlannedPackage> mBuildOrder = new ArrayList  <PlannedPackage>();

    /**Warning message
     * @attribute
     */
    private static final String mAnyBuildPlatforms = "Warning. The following package versions are not reproducible on any build platform: ";

    /**Flag to control output to standard out
     * @attribute
     */
    private boolean mAnyBuildPlatformsFlag = true;

    /**Warning message
     * @attribute
     */
    private static final String mAssocBuildPlatforms = "Warning. The following package versions are not reproducible on the build platforms associated with this baseline: ";

    /**Flag to control output to standard out
     * @attribute
     */
    private boolean mAssocBuildPlatformsFlag = true;

    /**Warning message
     * @attribute
     */
    private static final String mNotInBaseline = "Warning. The following package versions are not reproducible as they are directly dependent upon package versions not in the baseline: ";

    /**Flag to control output to standard out
     * @attribute
     */
    private boolean mNotInBaselineFlag = true;

    /**Warning message
     * @attribute
     */
    private static final String mDependent = "Warning. The following package versions are not reproducible as they are directly/indirectly dependent upon not reproducible package versions: ";

    /**Flag to control output to standard out
     * @attribute
     */
    private boolean mDependentFlag = true;

    /**Warning message
     * @attribute
     */
    private static final String mCircularDependency = "Warning. The following package versions are not reproducible as they have circular dependencies: ";

    /**Flag to control output to standard out
     * @attribute
     */
    private boolean mCircularDependencyFlag = true;

    /** String used to terminate lines
     * @attribute
     */
    private static final  String mlf = System.getProperty("line.separator");

    /** XML File Prefix
     */
    private static final String mXmlHeader = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>" + mlf;

    /**RippleEngine constructor
     * @param releaseManager  - Associated releaseManager instance
     * @param rtagId         - Release Identifier
     * @param isDaemon        - Mode of operation. False: Escrow, True: Daemon
     */
    public RippleEngine(ReleaseManager releaseManager, int rtagId, boolean isDaemon)
    {
        mLogger.debug("RippleEngine rtag_id {} isDaemon {}", rtagId, isDaemon);
        mReleaseManager = releaseManager;
        mBaseline = rtagId;
        mRtagId = rtagId;
        mDaemon = isDaemon;
        mReleaseManager.setDaemonMode(mDaemon);
    }

    /**
     * getRtagId
     * @return The rtagId of the Release attached to this instance of the RippleEngine
     */
    public int getRtagId()
    {
        return mRtagId;
    }

    /**Plan what is to be built
     *  <br>Discards all build file content
     *  <br>Generates new build file content
     * 
     * @param lastBuildActive           - False. Daemon Mode. The last build was a dummy. 
     *                                This planning session may not result in a build
     *                              - True. Daemon Mode. The last build was not a dummy.
     *                                There is a very good chance that this planning session
     *                                will result in a build, so it is given priority to connect 
     *                                to the database.  
     */
    public void planRelease(final boolean lastBuildActive) throws SQLException, Exception
    {
        mLogger.warn("planRelease mDaemon {}", mDaemon);

        //
        //  Diagnostic output
        //  Having issues with memory usage
        //
        long totalMem = Runtime.getRuntime().totalMemory();
        long freeMem = Runtime.getRuntime().freeMemory();
        mLogger.warn("Memory Usage: Total: {}, Free: {}, Used: {}", totalMem, freeMem, totalMem - freeMem);
        
        mBuildCollection.clear();
        mPackageCollection.clear();
        mPackageCollectionRipple.clear();
        mPackageCollectionTest.clear();
        mPackageCollectionWip.clear();
        mPackageCollectionAll.clear();
        mBuildOrder.clear();
        mEscrowRawData = "";
        mEscrowSetup = "";
        Phase phase = new Phase("Plan");

        // use finally block in planRelease to ensure the connection is released
        try
        {
            phase.setPhase("connectForPlanning");
            mReleaseManager.connectForPlanning(lastBuildActive);

            if ( mDaemon )
            {
                // claim the mutex
                mLogger.warn("planRelease claimMutex");
                phase.setPhase("claimMutex");
                mReleaseManager.claimMutex();

                // Populate the mBuildExclusionCollection
                //
                // Builds are either 'Directly excluded' or 'Indirectly excluded'
                // Direct excludes result from:
                //      User request
                //      Build failure
                //      Inability to build package
                // Indirectly excluded packages result from have a build dependency on a package
                // that is directly excluded.
                //  
                // In the following code we will extract from the Database all build exclusions
                // We will then add 'Relevant' entries to mBuildExclusionCollection
                // and delete, from the database those that are no longer relevant - ie indirectly excluded
                // items where the root cause is no longer in the set.
                phase.setPhase("mBuildExclusionCollection");
                mBuildExclusionCollection.clear();
                ArrayList<BuildExclusion> tempBuildExclusionCollection = new ArrayList<BuildExclusion>();

                mLogger.debug("planRelease queryBuildExclusions");
                mReleaseManager.queryBuildExclusions(tempBuildExclusionCollection, mBaseline);

                // only populate mBuildExclusionCollection with tempBuildExclusionCollection entries which have a relevant root_pv_id
                // The entry is relevant if:
                //     It is for a directly excluded package,other than a RippleStop
                //     It is for an indirectly excluded package AND the reason for exclusion still exists
                //

                for (Iterator<BuildExclusion> it = tempBuildExclusionCollection.iterator(); it.hasNext(); )
                {
                    BuildExclusion buildExclusion = it.next();

                    if ( buildExclusion.isRelevant(tempBuildExclusionCollection) )
                    {
                        mBuildExclusionCollection.add(buildExclusion);
                    }
                    else
                    {
                        // Remove the indirectly excluded entry as its root cause
                        // is no longer present.
                        buildExclusion.includeToBuild(mReleaseManager, mBaseline);
                    }
                }
            }

            //-----------------------------------------------------------------------
            //  Query package versions
            //
            phase.setPhase("queryPackageVersions");
            mLogger.debug("planRelease queryPackageVersions");
            mReleaseManager.queryPackageVersions(this, mPackageCollection, mBaseline);
            
            phase.setPhase("queryPackageWips");
            mReleaseManager.queryWips(this, mPackageCollectionWip, mBaseline);
            mReleaseManager.queryTest(this, mPackageCollectionTest, mBaseline);
            mReleaseManager.queryRipples(this, mPackageCollectionRipple, mBaseline);
            
            mPackageCollectionAll.addAll(mPackageCollection);
            mPackageCollectionAll.addAll(mPackageCollectionWip);
            mPackageCollectionAll.addAll(mPackageCollectionTest);
            mPackageCollectionAll.addAll(mPackageCollectionRipple);
            
            // Sort the collection by PVID
            //      Unit Test output order is known
            //      May assist in creating repeatable build orders
            //
            Collections.sort(mPackageCollectionAll.mCollection, Package.SeqComparator);

            //------------------------------------------------------------------------
            //    Process packages collected
            //    Determine and tag those we can't build
            phase.setPhase("processPackages");
            processPackages();

            //-----------------------------------------------------------------------
            //    At this point we have tagged all the packages that we cannot build
            //    Now we can determine what we are building
            phase.setPhase("planBuildOrder");
            mLogger.debug("planRelease process Remaining");
            planBuildOrder();

            //  Report excluded packages and the build plan
            //  This is being done with the MUTEX being held, but the 
            //  trade off is the cost of getting a connection.
            //
            if ( mDaemon )
            {
                phase.setPhase("Report Change");
                reportChange();
                phase.setPhase("Report Plan");            
                reportPlan();
            }
            
            //
            //  Generate the build Files
            //
            phase.setPhase("generateBuildFiles");
            generateBuildFiles();
        }
        finally
        {
            mLogger.debug("planRelease finally");
            // this block is executed regardless of what happens in the try block
            // even if an exception is thrown
            // ensure the SELECT FOR UPDATE is released
            try
            {
                if ( mDaemon )
                {
                    // attempt to release the SELECT FOR UPDATE through a commit
                    // a commit must be done in the normal case
                    // a commit may as well be done in the Exception case
                    // in the case of a SQLException indicating database connectivity has been lost
                    // having a go at the commit is superfluous
                    // as the SELECT FOR UPDATE will have been released upon disconnection
                    phase.setPhase("releaseMutex");
                    mReleaseManager.releaseMutex();
                }
            }
            finally
            {
                // ensure disconnect under all error conditions
                phase.setPhase("disconnectForPlanning");
                mReleaseManager.disconnectForPlanning(lastBuildActive);
            }
        }

        //
        //  Attempt to release objects that have been created during the planning phase
        //
        mPackageCollection.clear();
        mPackageCollectionRipple.clear();
        mPackageCollectionTest.clear();
        mPackageCollectionWip.clear();
        mPackageCollectionAll.clear();
        mBuildOrder.clear();
        
        mLogger.warn("planRelease mDaemon {} returned", mDaemon);
        phase.setPhase("EndPlan");
    }
    
    /** Process packages that have been collected as a part of the plan
     * @param phase
     * @throws SQLException
     * @throws Exception
     */
    private void processPackages() throws SQLException, Exception {

        // Deal with test builds here as they may impact upon package attributes
        //    eg: dependency collection and build standard differences
        //    Note: Done before mPackageDependencyCollection is setup
        //
        Phase phase = new Phase("processPackages");
        if ( mDaemon )
        {
            //  Exclude packages that have been specifically excluded because
            //      They have failed to build
            //      User says so
            //  Only examine the ReleaseCollection, Wip and Ripple Collections
            //      Can testBuild a package that has been excluded
            //      
            phase.setPhase("Exclude packages");
            for (ListIterator<BuildExclusion> it = mBuildExclusionCollection.listIterator(); it.hasNext(); )
            {
                BuildExclusion be = it.next();
                mLogger.debug("BE: {}", be);
                if ( be.isARootCause() )
                {
                    Package rootPackage = mPackageCollectionAll.contains(be.mId);
                    if (rootPackage != null) {
                    
                        Package p;
                        p = mPackageCollection.contains(rootPackage.mAlias);
                        if (p != null) {
                            p.excludeFromBuilds(-3, "Exclude from Release {}");
                        }
                        
                        p = mPackageCollectionWip.contains(be.mId);
                        if (p != null) {
                            p.excludeFromBuilds(-3, "Exclude from WIP {}");
                        }
                        
                        p = mPackageCollectionTest.contains(be.mId);
                        if (p != null) {
                            p.excludeFromBuilds(-3, "Exclude from Test {}");
                        }
                    }
                }
            }
            
            // Process test builds - they are in their own collection
            phase.setPhase("TestBuilds");
            for (Iterator<Package> it = mPackageCollectionTest.iterator(); it.hasNext(); )
            {
                Package p = it.next();
                mLogger.debug("planRelease package test build {}", p.mAlias);

                //
                //    Cannot test build an SDK based package or a Pegged Package
                //        Remove the test build request from the database
                //        Send a warning email
                //
                if(p.mIsPegged || p.mIsSdk)
                {
                    String reason;
                    reason = (p.mIsPegged) ? "Pegged" : "SDK Based";

                    mLogger.warn("planRelease Daemon Instruction (testBuild) of {} package deleted: {}", reason, p.mAlias);
                    mReleaseManager.markDaemonInstCompleted( p.mTestBuildInstruction );
                    emailRejectedDaemonInstruction("Cannot 'Test Build' a " + reason + " package",p);
                }

                // force patch for test build numbering
                p.mDirectlyPlanned = true;
                p.mChangeType.setPatch();
                p.mRequiresSourceControlInteraction = false;
                p.mBuildReason = BuildReason.Test;
                p.mIndirectlyPlanned = true;
            }
        }

        // Set up mPackageDependencyCollection on each package
        //    Examine the dependencies by alias and convert this to a 'package' selected from the released package set
        phase.setPhase("setPackageDependencyCollection");
        mLogger.debug("planRelease setup setPackageDependencyCollection");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            for (Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator(); it2.hasNext(); )
            {
                PkgDependency depEntry = it2.next();
                Package dependency = mPackageCollection.contains(depEntry.alias);
                if (dependency != null)
                        depEntry.pkg = dependency;
            }
        }    

        // Detect and deal with circular dependencies
        // Examine all packages in the Release. Other packages will be examined later
        phase.setPhase("Detect Circular Dependencies");
        mLogger.debug("planRelease deal with circular dependencies");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            if ( p.hasCircularDependency( mPackageCollection ) )
            {
                mLogger.info("planRelease circular dependency detected {}", p.mAlias);
                
                //  Force this package to be marked as having a circular dependency - even if its been excluded
                p.mBuildFile = 0;
                
                // Exclude the package
                // max 50 chars
                rippleBuildExclude(p, p.mId, "Package has circular dependency", -6);

                // take the package out of the build
                standardOut(mCircularDependency, p.mAlias, mCircularDependencyFlag);
                mCircularDependencyFlag = false;
            }
        }

        // Scan for packages with missing dependencies
        //    ie: The dependent package is not in the package Collection
        phase.setPhase("Scan missing dpendencies");
        mLogger.debug("planRelease use the fully built mPackageDependencyCollection");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            if ( mDaemon )
            {
                //  Daemon Mode Only
                //  Not interested in the dependencies of a pegged package or a package provided from an SDK.
                //  Such packages will be deemed to not have missing dependencies
                if (p.mIsPegged || p.mIsSdk)
                {
                    continue;
                }
            }

            for (Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator(); it2.hasNext(); )
            {
                PkgDependency depEntry = it2.next();
                Package dependency = mPackageCollection.contains(depEntry.alias);

                if (dependency == null)
                {
                    mLogger.info("planRelease dependency is not in the baseline {}", depEntry);
                    // Exclude the package
                    // max 50 chars
                    rippleBuildExclude(p, p.mId, "Package build dependency not in the release", -4);

                    // take the package out of the build
                    standardOut(mNotInBaseline, p.mAlias, mNotInBaselineFlag);
                    mNotInBaselineFlag = false;
                    break;
                }
            }
        }

        // Detect packages with no build standard and exclude them from the build
        phase.setPhase("Scan not reproducible");
        mLogger.debug("planRelease process packages which are not reproducible");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            if (p.mBuildFile == 0)
            {
                if ( mDaemon )
                {
                    //  Daemon Mode Only
                    //  Not interested in the reproducibility of a pegged package or a package provided from an SDK.
                    //  Such packages will be deemed to be reproducible - but out side of this release
                    if (p.mIsPegged || p.mIsSdk)
                    {
                        continue;
                    }
                }

                // Does the package have a build standard. If not then we can't reproduce it.
                // Escrow - Assume the package is provided
                // Daemon - Exclude this package, but not its consumers. If the package is available then we can use it.
                if (!p.isReproducible())
                {
                    mLogger.info("planRelease package not reproducible {}" ,p.mName);
                    
                    // Exclude the package
                    // max 50 chars
                    rippleBuildExclude(p, p.mId, "Package has no build environment", -1);

                    // package is not reproducible, discard
                    standardOut(mAnyBuildPlatforms, p.mAlias, mAnyBuildPlatformsFlag);
                    mAnyBuildPlatformsFlag = false;
                }
            }
        }

        //    Process packages which are not reproducible on the configured set of build machines.
        //
        //    Test each package and determine if the package contains a buildStandard that
        //    can be processed by one of the machines in the build set
        //
        //    ie: Package: Win32:Production and we have a BM with a class of 'Win32'
        //
        //    Only exclude the failing package and not its dependents
        //    May be legitimate in the release
        //
        phase.setPhase("Scan not reproducible2");
        mLogger.debug("planRelease process packages which are not reproducible2");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            if (p.mBuildFile == 0)
            {
                if ( mDaemon )
                {
                    //  Daemon Mode Only
                    //  Not interested in the reproducibility of a pegged package or a package provided from an SDK.
                    //  Such packages will be deemed to be reproducible - but out side of this release
                    if (p.mIsPegged || p.mIsSdk)
                    {
                        continue;
                    }
                }

                // package has yet to be processed
                // assume it does not need to be reproduced for this baseline
                //
                //    For each machineClass in the buildset
                boolean reproduce = false;

                for (Iterator<String> it2 = mReleaseManager.mReleaseConfigCollection.mMachineClasses.iterator(); it2.hasNext(); )
                {
                    String machineClass = it2.next();

                    if ( p.canBeBuildby(machineClass))
                    {
                        reproduce = true;
                        mLogger.info("planRelease package built on {} {}", machineClass, p.mAlias );
                        break;
                    }
                }

                if ( !reproduce )
                {
                    mLogger.info("planRelease package not reproducible on the build platforms configured for this baseline {}", p.mName);

                    // Exclude the package
                    // max 50 chars
                    rippleBuildExclude(p, p.mId, "Package not built for configured platforms", -2);

                    // package is not reproducible on the build platforms configured for this baseline, discard
                    standardOut(mAssocBuildPlatforms, p.mAlias, mAssocBuildPlatformsFlag);
                    mAssocBuildPlatformsFlag = false;
                }
            }
        }      

        if (mDaemon )
        {

            //  Daemon Mode Only
            //  Process packages which need to be ripple built
            phase.setPhase("Scan for ripples");
            mLogger.debug("Process packages which need to be ripple built");
            for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if (p.mBuildFile == 0)
                {
                    //  Not interested in a pegged package or a package provided from an SDK.
                    //  Such packages are not rippled
                    if (p.mIsPegged || p.mIsSdk)
                    {
                        continue;
                    }

                    //    Examine this packages dependencies
                    //    If one of them does not exist in the 'official release' set then
                    //    the package needs to be built against the official release set
                    //    and we can't build any of its dependent packages
                    Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator();
                    while ( it2.hasNext() )
                    {
                        PkgDependency depEntry = it2.next();

                        if ( !depEntry.pkg.mAdvisoryRipple )
                        {
                            // not advisory, ie: has ripple build impact
                            if ( !isInRelease(depEntry.pvId) )
                            {
                                // the package is out of date
                                // exclude all dependent package versions
                                mLogger.info("planRelease package out of date {}", p.mName);
                                p.mBuildReason = BuildReason.Ripple;
                                rippleIndirectlyPlanned(p);
                                
                                //  This package needs to be rippled
                                //  If this package has a rippleStop marker of 's', then we cannot
                                //  build this package at the moment.
                                //  Packages that depend on this package have been excluded
                                //  We need to exclude this one too
                                //
                                if (p.mRippleStop == 's' || p.mRippleStop == 'w') 
                                {
                                    // Package marked as a rippleStop
                                    // max 50 chars
                                    rippleBuildExclude(p, -2, "Ripple Required." + " Waiting for user", -11);

                                    if (p.mRippleStop == 's' ) {
                                        
                                        // Need to flag to users that the package build is waiting user action
                                        mLogger.info("planRelease Ripple Required. Stopped by flag {}", p.mName);
                                        mReleaseManager.setRippleStopWait(mRtagId,p);
                                        emailRippleStop(p);
                                    }
                                    
                                }
                                
                                break;
                            }
                        }
                    }
                }
            }

            //  Daemon Mode Only
            //  Process packages which do not exist in the archive
            //  For unit test purposes, assume all packages exist in the archive if released
            mLogger.debug("planRelease process packages which do not exist in the archive");
            phase.setPhase("Scan dpkg_archive");
            if ( mReleaseManager.mUseDatabase )
            {
                for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
                {
                    Package p = it.next();

                    if (p.mBuildFile == 0)
                    {
                        // package has yet to be processed
                        if (!p.mDirectlyPlanned && !p.mIndirectlyPlanned && p.mForcedRippleInstruction == 0)
                        {
                            // check package version archive existence
                            if (!p.existsInDpkgArchive())
                            {
                                if (! p.mIsBuildable)
                                {
                                    //  Package does not exist in dpkg_archive and it has been flagged as unbuildable
                                    //  This may be because is Unbuildable or Manually built
                                    mLogger.info("planRelease Unbuildable package not found in archive {}", p.mName);
                                    // Exclude the package
                                    // max 50 chars
                                    rippleBuildExclude(p, p.mId, "Unbuildable" + " package not found in archive", -10);
                                    
                                }
                                //  Not interested in a pegged package or a package provided from an SDK.
                                //  Such packages are not rippled
                                else if (p.mIsPegged || p.mIsSdk)
                                {
                                    String reason;
                                    reason = (p.mIsPegged) ? "Pegged" : "SDK";

                                    //  Pegged packages or packages provided from an SDK MUST exist in dpkg_archive
                                    //  They will not be built within the context of this release. It is the responsibility
                                    //  of another release to build them.
                                    mLogger.info("planRelease {} package not found in archive {}", reason, p.mName);
                                    // Exclude the package
                                    // max 50 chars
                                    rippleBuildExclude(p, p.mId, reason + " package not found in archive", -7);
                                }
                                else if (p.mForcedRippleInstruction == 0)
                                {
                                    //  [JATS-331] Unable to rebuild package with Advisory Ripple dependencies
                                    //    Examine this packages dependencies
                                    //    If one of them does not exist in the 'official release' set then
                                    //    the package cannot be rebuilt in this release.
                                    for (Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator() ; it2.hasNext() ;)
                                    {
                                        PkgDependency dpvId = it2.next();
                                        if ( !isInRelease(dpvId.pvId) )
                                        {
                                            // This package cannot be rebuilt as one of its dependents is NOT in this release
                                            // exclude all dependent package versions
                                            
                                            mLogger.info("planRelease package not found in archive. Cannot be rebuilt due to {}", p.mName);
                                            // Exclude the package
                                            // max 50 chars
                                            rippleBuildExclude(p, p.mId, "Package cannot be rebuilt in this release", -4);
                                            break;
                                        }
                                    }
                                    
                                    //  The package has not been excluded from the build
                                    if (p.mBuildFile == 0)
                                    {
                                        mLogger.info("planRelease package not found in archive {}", p.mName);
                                        // DEVI 47395 the cause of this build is not WIP or ripple induced,
                                        // it simply does not exist in the archive (has been removed)
                                        // prevent source control interaction
                                        p.mRequiresSourceControlInteraction = false;
                                        p.mBuildReason = BuildReason.Restore;
                                        rippleIndirectlyPlanned(p);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            //  Daemon Mode Only
            //  Detect bad forced ripples requests and reject them
            mLogger.debug("planRelease process forced ripples");
            phase.setPhase("Test bad ripple requests");
            for (Iterator<Package> it = mPackageCollectionRipple.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if (p.mBuildFile == 0)
                {
                    //
                    //    Cannot force a ripple on an SDK based package or a Pegged Package
                    //        Remove the daemon instruction from the database
                    //        Send a warning email
                    //
                    if(p.mIsPegged || p.mIsSdk)
                    {
                        String reason;
                        reason = (p.mIsPegged) ? "Pegged" : "SDK Based";

                        mLogger.warn("planRelease Daemon Instruction of {} package deleted: {}", reason, p.mName);
                        mReleaseManager.markDaemonInstCompleted( p.mForcedRippleInstruction );
                        emailRejectedDaemonInstruction("Cannot 'Ripple' a " + reason + " package",p);

                        p.mBuildFile = -8; 
                    }
                }
            }

            //  Daemon Mode Only
            //  Mark Pegged and SDK packages as not to be built
            //  Mark RippleStoped packages as not to be build
            //  Have previously detected conflicts between pegged/sdk packages and daemon instructions
            //
            mLogger.debug("planRelease remove pegged and SDK packages from the build set");
            phase.setPhase("Remove Pegged and SDKs");
            for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if (p.mBuildFile == 0)
                {
                    String reason = null;
                    
                    //  Not interested in a pegged package or a package provided from an SDK or packages with an active RippleStop
                    //  Such packages are not built
                    if (p.mIsPegged ) {
                        reason = "Pegged";
                        p.mBuildFile = -8;
                    } else if (p.mIsSdk) {
                        reason = "SDK";
                        p.mBuildFile = -8;
                    } else if (p.mRippleStop == 's' || p.mRippleStop == 'w') {
                        reason = "RippleStop";
                        p.mBuildFile = -11;
                    }
                    
                    if (reason != null)
                    {
                        mLogger.info("planRelease {} not built in this release {}", reason, p.mName);
                    }
                }
            }
        }
        else
        {
            // escrow reporting only
            // Report packages that are not reproducible
            //
            //  Note: I don't believe this code can be executed
            //        The -3 is only set in daemon mode
            for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if (p.mBuildFile == -3)
                {
                    standardOut(mDependent, p.mAlias, mDependentFlag);
                    mDependentFlag = false;
                }
            }
        }
        phase.setPhase("End");
    }
    
    /** Plan the build order.
         *  Assumes that a great deal of work has been done.
         *  This is a stand alone method to contain the work
         *  
         * @throws Exception
         * @throws SQLException
         */
        private void planBuildOrder() throws Exception, SQLException 
        {
            
        /**
         * Current status
         *  basicPlan - time to complete current ripple
         *  FullPlan - Include all WIPS/RIPPLES into the release set
         *  
         *  If the full Plan does NOT extend the time of the basicPLan by 20% then use the full plan
         *  otherwise complete the current ripple and the include WIPs/RIPPLEs\
         *  
         *  TEST requests will be done first.
         *
         */
        
            // Process remaining packages which are need to be reproduced for this baseline.
            //    Determine the build file for each package
            //    For daemon builds:
            //      Determine the next package that can be built now
            //          Sounds simple - doesn't it
            //      Set its mBuildNumber to 1, all remaining reproducible packages to 2
            //    For escrow builds:
            //      Determine the package versions that can be built in the build iteration
            //      Set their mBuildNumber to the build iteration
            //      Increment the build iteration and repeat until all package versions 
            //      that need to be reproduced have been assigned a build iteration        
            
            //
            //  Generate a plan for the packages that are in the current release
            //  These may be the result of a ripple going through the system 
            //  or a rebuild of a missing package or the result of a merge.
            //
            //  This will provide our basic plan, before we consider adding new packages to the
            //  mix, such as those from user WIPS and RIPPLE requests.
            //  
            PlanResults basicPlan = planCollection("Basic", mPackageCollection, true);
            addWipsAndRipples (basicPlan);
            
            //  Have not considered any of the WIP or RIPPLE packages
            //  If any of them are in the basic plan, then we can ignore them as we will build them
            //  when we build the basic plan
            //
            //  Determine a set of packages that will not be included if we build the basic plan
            //
            //  At the moment - lets assume there are none
            //  What do we do ?
            //      Select the first request
            //      Replace the package in the released collection
            //      Run the plan algorithm
            //      Restore the release collection
            //
            if ( mDaemon )
            {
                //
                //  Need to consider the TEST requests
                //      - Valid requests will be placed first on the build order
                //
                mBuildOrder.clear();
                for (Iterator<Package> it = mPackageCollectionTest.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    if (p.mBuildFile >= 0)
                    {
                        mBuildOrder.add( new PlannedPackage(p) );
                    }
                }
                
                //
                //  Examine Plan control
                //      DropPlan - do not use the basic plan. This is a one-shot request
                //      Threshold == 0. Always use a full plan
                if (mPlanControl.mDumpPlan || mPlanControl.mThreshold == 0)
                {
                    mLogger.warn("Drop basicPlan and use fullPlan");
                    basicPlan.planTime = 0;
                    basicPlan.planCollection.clear();
                    
                    //  Always reset the one-shot request to drop the current plan
                    mReleaseManager.resetPlanControl(mBaseline);
                }
                
                //  First attempt
                //  Insert all the WIPs and RIPPLES into the buildset. 
                //  Generate a plan and see how much the time is extended.
                //
                //  Don't modify mPackageCollection, although data in the 'Package' in the underlying packages may change
                
                PlanResults fullPlan = postRipplePlan("Full", basicPlan, true);
                
                //  Decide with plan to use
                //  At the moment we have, at most two.
                //      - (basic) Current Release ripples
                //      - (full) Current Release + All WIPS and RIPPLES
                //
                //  If we have both then use the planContol threshold to determine which plan to use
                //  The threshold number is taken as a percent by which the fullPlan Time impacts basicPlan time
                //  before the fullPlan will be rejected.
                //  ie: threshold = 20.  If fullPlan is  20% longer that the basicPlan, then use the basicPlan then a modified fullPlan (Cautious)
                //  ie: threshold = 100. If fullPlan is 100% longer that the basicPlan, then use the basicPlan then a modified fullPlan (Aggressive)
                //
                //  A modified fullPlan is one without the natural ripples as these will have been done in the basicPlan
                //
                //  Add the planned packages into the buildOrder
    
                if( !basicPlan.planCollection.isEmpty() && !fullPlan.planCollection.isEmpty() )
                {
                    //  Calculate the build impact as a percentage
                    //  The impact of the fullPlan over the basicPlan
                    //
                    int buildImpact = 0;
                    if (fullPlan.planTime != basicPlan.planTime &&  basicPlan.planTime  != 0) {
                        buildImpact = ((fullPlan.planTime - basicPlan.planTime) * 100) / (basicPlan.planTime);
                    }
                    mLogger.warn("Two plan selection: Basic:{} Full:{}, Impact:{}% Threshold:{}% Exceeded: {}", basicPlan.planTime,fullPlan.planTime, buildImpact, mPlanControl.mThreshold, buildImpact >= mPlanControl.mThreshold);

                    if ( buildImpact >= mPlanControl.mThreshold )
                    {
                        //  BuildImpact too high
                        //  Use the basic plan FOLLOWED by a plan that does not include
                        //      Ripples done by the basic plan
                        //      WIPs/RIPPLES done in the course of doing the basic plan
                        mLogger.warn("Use basic plan, then modified full plan");
                        mBuildOrder.addAll(basicPlan.planCollection);

                        fullPlan = postRipplePlan("Full-Ripples", basicPlan, false);
                        
                        //  Insert a marker package to indicate a 'newPlan'
                        PlannedPackage pp = new PlannedPackage( new Package() );
                        pp.mBuildLevel = -1;
                        mBuildOrder.add(pp);                     

                        mBuildOrder.addAll(fullPlan.planCollection);
                        
                    } else {
                        //  Use the full plan
                        mLogger.warn("Use full plan");
                        mBuildOrder.addAll(fullPlan.planCollection);
                    }
                } else if (!basicPlan.planCollection.isEmpty() ) {
                    // Use the basic plan
                    mLogger.warn("Use basic plan");
                    mBuildOrder.addAll(basicPlan.planCollection);
                    
                } else if ( !fullPlan.planCollection.isEmpty() ) {
                    // Use the full plan
                    mLogger.warn("Use full plan");
                    mBuildOrder.addAll(fullPlan.planCollection);
                    
                } else {
                    // Do not have a plan
                    // May have tests requests
                    mLogger.warn("Use NO plan");
                }
                
                //
                //  Now have a build order
                //  Allocate new version numbers
                //      Examine packages with a buidLevel of zero
                //  If we fail all of them then we can't build anything
                //
                mLogger.warn("Determine new version numbers");
                Package build = ReleaseManager.NULL_PACKAGE;
                for (Iterator<PlannedPackage> it = mBuildOrder.iterator(); it.hasNext(); )
                {
                    PlannedPackage pkg = it.next();
                    if (pkg.mBuildLevel != 0)
                    {
                        continue;
                    }
                    
                    //
                    //  Attempt to allocate a new version number
                    //  If we can't generate a new version number, then this is considered to be a build failure
                    //  The package will be excluded and the user will be emailed
                    //
                    Package p = pkg.mPkg;

                    int pvApplied = p.applyPV(mReleaseManager);

                    if ( pvApplied == 0)
                    {
                        build = p;
                        break;
                    }
                    else if ( pvApplied == 1 )
                    {
                        // max 50 chars
                        rippleBuildExclude(p, p.mId, "Package has non standard versioning", -12);
                    }
                    else if ( pvApplied == 2 )
                    {
                        // max 50 chars
                        rippleBuildExclude(p, p.mId, "Package has reached ripple field limitations", -12);
                    }
                    else if ( pvApplied == 3 )
                    {
                        // max 50 chars
                        rippleBuildExclude(p, p.mId, "Package has invalid change type", -12);
                    }
                    else
                    {
                        // max 50 chars
                        // Bad programming - should not get here
                        rippleBuildExclude(p, p.mId, "Unable to calculate next version", -12);
                    }
                }
                
                
                //  Now have an mBuildOrder
                //  Mark the selected package in the build order as the one to be built
                //  May need to process its it a bit more
                //  
                if ( build != ReleaseManager.NULL_PACKAGE)
                {
                    build.mBuildFile = 1;
                    
                    if ( build.mForcedRippleInstruction > 0 )
                    {
                        mReleaseManager.markDaemonInstCompleted( build.mForcedRippleInstruction );
                    }
    
                    if ( build.mTestBuildInstruction > 0 )
                    {
                        mReleaseManager.markDaemonInstInProgress( build.mTestBuildInstruction );
                    }
                    
                    //  Now that we know which package we are building
                    //      Set the previously calculated nextVersion as the packages version number
                    //      Claim the version number to prevent other builds from using it. Even if doing a test build
                    //
                    mLogger.debug("Update mVersion: {}", build);
                    if (build.mNextVersion != null)
                    {
                        mReleaseManager.claimVersion(build.mPid, build.mNextVersion + build.mExtension, mBaseline);
                        build.mVersion = build.mNextVersion;
                        mLogger.warn("Update mVersion: {} to {}", build, build.mVersion);
                    }
                
                    //
                    //  Ensure that the package we are about to build is present in the mPackageCollection as its
                    //  this list that is used to generate the build.xml file - that may be a bad way to do it , but ...
                    //
                    //  Note: Can't really modify others as they will be used as dependencies
                    //
                    mPackageCollection.upsert(build);
                    build.mIsNotReleased = false;
                }

                //
                //  Report packages that are indirectly excluded
                //  ie: They will not be built because one, or more, of their dependents is not buildable
                //
                mLogger.warn("planBuildOrder process packages which are not ripple buildable");

                Package.resetCircularDependency (mPackageCollection);
                for (ListIterator<BuildExclusion> it = mBuildExclusionCollection.listIterator(); it.hasNext(); )
                {
                    BuildExclusion be = it.next();
                    
                    //  Only process direct exclusions
                    //      mBuildExclusionCollection is at this point based on relevant (direct and indirect) 
                    //      excluded pv's in the database
                    //
                    if ( be.isARootCause() )
                    {

                        for (Iterator<Package> it1 = mPackageCollectionAll.iterator(); it1.hasNext(); )
                        {
                            Package p = it1.next();

                            // TODO - Do we need && p.mTestBuildInstruction == 0 && p.mForcedRippleInstruction == 0

                            if ( be.compare(p.mId)  )
                            {
                                // package is not reproducible, discard it and its consumers
                                //
                                mLogger.warn("Excluded Package {}, {}", p, be );                                     

                                ArrayList<Package> toExclude = new ArrayList<Package>();
                                toExclude.addAll(usedByAnyPackages(p, mPackageCollection ));

                                //  Process packages that we need to exclude indirectly
                                //
                                while ( ! toExclude.isEmpty())
                                {
                                    Package pkg = toExclude.remove(0);

                                    // If this package has not been excluded (for whatever reason), than add it
                                    boolean found = false;
                                    for (Iterator<BuildExclusion> it2 = mBuildExclusionCollection.iterator(); it2.hasNext(); )
                                    {
                                        BuildExclusion buildExclusion = it2.next();
                                        if (buildExclusion.compare(pkg.mId) )
                                        {
                                            found = true;
                                            if  ( buildExclusion.isImported() )
                                            {
                                                // An exclusion for this package already exists
                                                // If it was 'imported' from the database then 
                                                // mark it as processed so that it will be persisted
                                                //
                                                // Otherwise it will be a new one
                                                //
                                                buildExclusion.setProcessed();
                                            }
                                            break;
                                        }
                                    }

                                    if (!found)
                                    {
                                        BuildExclusion buildExclusion = new BuildExclusion(pkg.mId, p.mId, null, p.mTestBuildInstruction);
                                        it.add(buildExclusion);
                                        mLogger.warn("Indirectly Excluded Package {}", pkg.mAlias);
                                        pkg.mBuildFile = -5; 
                                    }

                                    // Determine all packages that use this excluded package
                                    // THAT WE HAVE NOT ALREADY PROCESSED - circular dependencies are a killer
                                    ArrayList<Package> usedBy = usedByAnyPackages(pkg, mPackageCollection );
                                    for (Iterator<Package> it2 = usedBy.iterator(); it2.hasNext(); )
                                    {
                                        Package uPkg = it2.next();
                                        if (!uPkg.mCheckedCircularDependency)
                                        {
                                            toExclude.add(uPkg);
                                            uPkg.mCheckedCircularDependency = true;
                                        }
                                    }

                                }
                            }
                        }
                    }
                }
                 
                 //
                 // Handle daemon Instructions that cannot be satisfied
                 // Need to be rejected to prevent continuous planning
                 //
                 mLogger.warn("Handle unsatisfied Daemon Instructions");
                 reportUnsatisfiedDaemonInstructions();
                 
                 //
                 //  Examine the build order and 'delete' entries with a -ve mBuildFile
                 //  These will be the product of pvApply errors and the packages that depend on them
                 //
                 for (Iterator<PlannedPackage> it = mBuildOrder.listIterator(); it.hasNext(); )
                 {
                     PlannedPackage pkg = it.next();
                     if (pkg.mPkg.mBuildFile < 0)
                     {
                         it.remove();
                         mLogger.warn("Purge mBuildOrder {}", pkg.mPkg);
                         continue;
                     }
                 }
                 
                 //
                 // If the first entry is a 'new Plan' marker, then remove it
                 //     It will be the result of applyPvs that failed
                 if ( !mBuildOrder.isEmpty() )
                 {
                     PlannedPackage pkg = mBuildOrder.get(0);
                     if (pkg.mBuildLevel < 0)
                         mBuildOrder.remove(0);
                 }
                
                //
                //  To fit in with the old algorithm ( ie: could be improved )
                //  Insert marks into all packages
                //  Not sure exactly why - Its used in the generation of the ant build file
                //                     Want to set mNoBuildReason, mBuildFile
                //
                //      Package we have selected to build: 0     , 1
                //      Package we could have built      : 0     , 2
                //      Packages we can't build          : reason, 3
                //      Packages that are OK             : 3     , 3
                //      ????                             : 0     , 3
                
                for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    if (p == build ) {
                        p.mNoBuildReason = 0;
                        p.mBuildFile = 1;
                    } 
                    else if ( p.mBuildFile < 0 )
                    {
                        p.mNoBuildReason = p.mBuildFile;
                        p.mBuildFile = 3;
                    }
                    else if (p.mBuildReason != null)
                    {
                        p.mNoBuildReason = 0;
                        p.mBuildFile = 2;
                    }
                    else
                    {
                        p.mNoBuildReason = 0;
                        p.mBuildFile = 3;
                    }
                }
    
            }
            else
            {
                //  Escrow
                //  The basic plan is the escrow build order
                mBuildOrder = basicPlan.planCollection;
                
                for (Iterator<PlannedPackage> it = mBuildOrder.iterator(); it.hasNext(); )
                {
                    PlannedPackage p = it.next();
                    p.mPkg.mNoBuildReason = p.mPkg.mBuildFile;
                    p.mPkg.mBuildFile = p.mBuildLevel + 1;
                }
            }
    
            mLogger.warn("Final Plan");
            for (Iterator<PlannedPackage> it = mBuildOrder.iterator(); it.hasNext(); )
            {
                PlannedPackage p = it.next();
                mLogger.warn("Plan: {} {} {}", p.mBuildLevel, p.mPkg.mAlias, p.mPkg.mId);
            }
               
    
        }

        /**
         * Post process the basic plan to replace packages with WIPs and RIPPLES
         * Done so that the displayed plan correctly indicates the time that these will be processed
         * 
         * @param basicPlan
         */
    private void addWipsAndRipples(PlanResults basicPlan) {
        for (Iterator<PlannedPackage> it = basicPlan.planCollection.iterator(); it.hasNext(); )
        {
            PlannedPackage pkg = it.next();
            Package p = mPackageCollectionWip.contains(pkg.mPkg.mAlias);
            if (p == null ) {
                p = mPackageCollectionRipple.contains(pkg.mPkg.mAlias); 
            }
            
            //  Have a WIP/RIPPLE will be processed in the basic plan
            //  If its eligible to be built then replace the package entry in the plan
            
            if (p != null && p.mBuildFile >= 0 ) {
                mLogger.warn("WIP/RIPPLE consumed into basic plan: {}", p);
                pkg.mPkg = p;
            }
        }    
        
    }

    /** Internal class to contain intermediate results
     */
    class PlanResults {
        int planTime = 0;
        ArrayList<PlannedPackage> planCollection = new ArrayList<PlannedPackage>(); 
    }

    /**
     * Process a collection of packages and generate a collection of package plans
     * A package plan is a collection of packages that can be built
     *      The first item in the list is a package that can be built right now
     *      Other items in the list will be packages that can be built now or will need to be built as the result
     *      of a ripple
     *      
     * The method will be called multiple times so that we can evaluate different plans
     * The underling packages will have data and flags that will need to be rested before a calculation      
     *
     * @param name - Name of the plan
     * @param packageCollection - Base collection of packages to plan
     * @param mode - true: Include 'Ripples'
     * 
     * @return
     */
    private PlanResults planCollection(String name, PackageCollection packageCollection, boolean mode)
    {
        Phase phase = new Phase("Plan-" + name); 
        ArrayList<PlannedPackage> ripplePlan = new ArrayList<PlannedPackage>();
        PlanResults results = new PlanResults();

        //  Reset flags used in the calculations
        Package.resetProcessed(packageCollection);
        
        
        //  Exclude all packages that cannot be built and all packages that depend on them
        //      First find packages to be directly excluded
        ArrayList<Package>exclude = new ArrayList<Package>();
        phase.setPhase("Exclude Unbuildable");
        for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
        {
            Package p = it.next();
            if ( p.mBuildFile == -8 ) {

                //  Mark SDK or Pegged so that we don't build them, but can build their dependents
                p.mProcessed = true;
                p.mIsProcessed = true;
                mLogger.warn("SDK/Peg Exclude: {}", p);
                
            } else if (p.mBuildFile < 0 ) {
                exclude.add(p);
                mLogger.warn("Direct Exclude: {}", p);
                
            }
        }
        
        //  Exclude packages that have failed to be built
        //      These are recorded in mBuildExclusionCollection
        phase.setPhase("Exclude Build Failures");
        for (ListIterator<BuildExclusion> it = mBuildExclusionCollection.listIterator(); it.hasNext(); )
        {
            BuildExclusion be = it.next();
            mLogger.warn("BE: {}", be);
            if ( !be.isAIndirectError() )
            {
                for (Iterator<Package> it1 = packageCollection.iterator(); it1.hasNext(); )
                {
                    Package p = it1.next();
                    if (p.mBuildFile >= 0 && p.mId == be.mId) {
                        p.mBuildFile  = -3;
                        mLogger.warn("AddExclude {}",p);
                        exclude.add(p);
                        break;
                    }
                }
            }
        }

        
        //  Process the list of packages to be excluded
        //  Add to the list packages that depend on the excluded package that have not already been excluded
        phase.setPhase("ExcludeAllUsed");
        while( !exclude.isEmpty() )
        {
            Package p = exclude.remove(0);
            mLogger.info("planCollection package not buildable {}. {},{}", p, p.mProcessed, p.mIsProcessed);
            
            p.mProcessed = true;                        // Used to indicate pkg has had its dependencies scanned
            p.mIsProcessed = true;                      // Used to indicate pkg has been added to list to process 


            for (Iterator<Package> it1 = packageCollection.iterator(); it1.hasNext(); )
            {
                Package pkg = it1.next();
                if (pkg.mProcessed) {
                    continue;
                }
                
                for (Iterator<PkgDependency> it = pkg.mDependencyCollection.iterator(); it.hasNext(); )
                {
                    PkgDependency depEntry = it.next();
                    if ( p.mAlias.compareTo( depEntry.alias ) == 0 ) {
                        if (!pkg.mIsProcessed) {
                            pkg.mIsProcessed = true;
                            exclude.add(pkg);
                        }
                    }
                }
            }
        }
        
        // Have a collection packages that we can build right now
        // Create the ripplePlan

        phase.setPhase("Calc Build Order");
        if (mDaemon)
        {
            ArrayList<PlannedPackage> toBuild = new ArrayList<PlannedPackage>();
            
            //
            //  Daemon Mode
            //  Generate the ripplePlan - collection of packages that we need to build in the current ripple
            //
            //  Determine if we have a reason to build anything in this collection of buildable packages
            //      Reset mProcessed - it will be used to detect that we have processed a package
            
            for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();
                if (!p.mIsProcessed) {
                    PlannedPackage pPkg = new PlannedPackage(p); 
                    
                    results.planCollection.add(pPkg);
                    pPkg.mProcessed = false;
                    
                    if (p.mBuildReason != null) {
                        if (mode || ( !mode && p.mBuildReason != BuildReason.Ripple )) {
                            toBuild.add(pPkg);
                        }
                    }
                }
            }
            
            //  Need (would like to) build stuff
            //  Determine the packages that we need to build and the time that it will take
            //
            if ( !toBuild.isEmpty() )
            {
                //  Locate the packages that we need to build
                //  The build order cannot be correctly determined at this time
                //      Need to add elements to the end of the list while processing
                //      Sum the buildTimes of the packages that we add to the list
                while ( ! toBuild.isEmpty())
                {
                    PlannedPackage pkg = toBuild.remove(0);
                    
                    if ( ! pkg.mProcessed )
                    {
                        ripplePlan.add(pkg);
                        results.planTime += pkg.mPkg.mBuildTime;
                        pkg.mProcessed = true;
                        
                        // How do we handle duplicates ?
                        ArrayList<PlannedPackage> usedBy = usedByPackages(pkg, results.planCollection);
                        toBuild.addAll(usedBy);
                    }
                }
            }
        }
        else
        {
            //  Escrow
            //      Add all (not excluded) packages directly to the ripplePlan
            for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();
                if (!p.mIsProcessed) {
                    ripplePlan.add(new PlannedPackage(p));
                }
            }  
        }
            
        //
        //  Now we have a collection of packages to build (and only the packages to build)
        //  Need to determine the order in which it can be done and assign build levels
        //  Basically:
        //      Scan the collection looking for any package whose dependencies do not exist in the collection
        //      or they have been allocated a build level.
        //
        //      Need to do this in waves so that we can correctly determine the build level.
        //
        //
        //  Need two metrics out of this process
        //      buildGroup - all packages in a given group depend entirely on packages in lower numbers groups
        //      buildLevel - This is a function of the number of parallel builds that can be performed
        //                   and 
        
        
        //
        for (Iterator<PlannedPackage> it = ripplePlan.iterator(); it.hasNext(); )
        {
            PlannedPackage p = it.next();
            p.mProcessed = true;
            p.mBuildLevel = 0;
        }
        
        int buildLevel = 0;
        boolean more = true;
        do  {
            ArrayList<PlannedPackage> thisLevel = new ArrayList<PlannedPackage>();
            for (Iterator<PlannedPackage> it = ripplePlan.iterator(); it.hasNext(); )
            {
                PlannedPackage p = it.next();
                if ( p.mProcessed )
                {
                    boolean found = false;
                    for (Iterator<PkgDependency> it2 = p.mPkg.mDependencyCollection.iterator(); !found && it2.hasNext(); )
                    {
                        PkgDependency depEntry = it2.next();
                        for (Iterator<PlannedPackage> it3 = ripplePlan.iterator(); !found && it3.hasNext(); )
                        {
                            PlannedPackage pkg = it3.next();
                            if (pkg.mPkg.mAlias.compareTo( depEntry.alias ) == 0)
                            {
                                found = pkg.mProcessed ;
                                break;
                            }
                        }
                    }
                    
                    if (!found)
                    {
                        //  None of this packages dependencies can be found in the collection of packages to build
                        //  Thus we can build it - at the current level
                        p.mBuildLevel = buildLevel;
                        thisLevel.add(p);
                    }
                }
            }
            
            //  Mark all the packages at this level as have been processed
            //  Cannot do this while we are determining the packages in the level
            for (Iterator<PlannedPackage> it = thisLevel.iterator(); it.hasNext(); )
            {
                PlannedPackage p = it.next();
                p.mProcessed = false;
            }
            
            //
            //  If any packages needed to be build at this level, then there might be a higher level
            //  If no packages needed to be build at this level, then there is nothing to do.
            more = !thisLevel.isEmpty(); 
            buildLevel++;
            
        } while (more);
        
        //  Sort the packages by buildOrder and buildTime
        phase.setPhase("Sort Plan");
        Collections.sort(ripplePlan, PlannedPackage.BuildOrderComparitor);
        
        //  Optionally - Display the calculated plan
        //      It will fill the log, but ...
        if (mLogger.isInfoEnabled() )
        {
            phase.setPhase("Display Build Order");
            mLogger.info("Plan Build {} Time: {}", name, results.planTime);
            for (Iterator<PlannedPackage> it = ripplePlan.iterator(); it.hasNext(); )
            {
                PlannedPackage p = it.next();
                mLogger.info("Plan: {} {} t:{}", p.mBuildLevel, p.mPkg.mAlias, p.mPkg.mBuildTime);
            }
        }
        
        results.planCollection = ripplePlan;

        phase.setPhase("EndPlanCollection");
        return results;
    }

    /** Generate a plan based on a modified set of packages
     * 
     * @param name      - Name of the plan
     * @param basicPlan - basicPlan
     * @param mode      - True: Include natural ripples and WIPS/RIPPLES that would have been done
     *                          as a part of the basic plan
     * @return
     */
    private PlanResults postRipplePlan(String name, PlanResults basicPlan, boolean mode) {

        PackageCollection fullPlanCollection = new PackageCollection(mPackageCollection);
        ArrayList<Package> buildCandidates = new ArrayList<Package>();
        buildCandidates.addAll(mPackageCollectionWip.mCollection);
        buildCandidates.addAll(mPackageCollectionRipple.mCollection);
        
        if ( !buildCandidates.isEmpty() )
        {
            for (Iterator<Package> it = buildCandidates.iterator(); it.hasNext(); )
            {
                Package p = it.next();
                if (p.mBuildFile >= 0)
                {
                    boolean include = true;
                    if (!mode)
                    {
                        //  Exclude packages that would have been processed in the basicPlan
                        for (Iterator<PlannedPackage> it1 = basicPlan.planCollection.iterator(); it1.hasNext(); )
                        {
                            PlannedPackage pkg = it1.next();
                            if (pkg.mPkg == p)
                            {
                                mLogger.warn("Test Plan without {}", p);
                                include = false;
                                break;
                            }
                        }
                    }
                    
                    if( include )
                    {
                        mLogger.warn("Test Plan with {}", p);
                        fullPlanCollection.upsert(p);
    
                        Package.resetCircularDependency (fullPlanCollection);
                        if ( p.hasCircularDependency( fullPlanCollection ) )
                        {
                            mLogger.info("planRelease circular dependency detected {}", p);
                            
                            //  Force this package to be marked as having a circular dependency - even if its been excluded
                            p.mBuildFile = 0;
                            
                            // Exclude the package
                            // max 50 chars
                            rippleBuildExclude(p, p.mId, "Package has circular dependency", -6);
                        }
                    }
                }
            }
            
        }
        return planCollection(name, fullPlanCollection, mode);
    }

    /** 
     *  Calculate a collection of packages that actively use the named package
     *  A consumer package does NOT use the named package,
     *      If the package is marked as advisoryRipple
     *      If the consumer is an SDK or is Pegged
     *      If the consumer cannot be built
     *      
     *  Used when planning which packages need to be built
     *   
     * @param pkg - Package to process
     * @param planCollection - collection of packages to scan
     *
     * @return A collection of packages that actively 'use' the specified package

     */
    private ArrayList<PlannedPackage> usedByPackages(PlannedPackage pkg, ArrayList<PlannedPackage> planCollection) {
        ArrayList<PlannedPackage> usedBy = new ArrayList<PlannedPackage>();
        
        //  If 'this' package is marked as advisory, then don't ripple its consumers
        if ( !pkg.mPkg.mAdvisoryRipple )
        {
            
            for (Iterator<PlannedPackage> it = planCollection.iterator(); it.hasNext(); )
            {
                PlannedPackage p = it.next();

                //  If the (potential) consumer is an SDK or is Pegged, then we don't ripple its consumer
                if  (!(p.mPkg.mIsSdk || p.mPkg.mIsPegged))
                {
                    //  Is this package 'actively used' in the current build
                    if (p.mPkg.mBuildFile >= 0)
                    {
                        for (Iterator<PkgDependency> it2 = p.mPkg.mDependencyCollection.iterator(); it2.hasNext(); )
                        {
                            PkgDependency depEntry = it2.next();
                            if (  pkg.mPkg.mAlias.compareTo( depEntry.alias ) == 0  ) {
                                //  Have found a consumer of 'pkg'
                                
                                usedBy.add(p);
                                break;
                            }
                        }
                    }
                }
            }
        }
        
        return usedBy;
    }
    
    /** 
     *  Calculate a collection of packages that use the named package
     *  
     *  Used when determining which packages to exclude from a build
     *   
     * @param pkg - Package to process
     * @param packageCollection - collection of packages to scan
     *
     * @return A collection of packages that actively 'use' the specified package

     */
    private ArrayList<Package> usedByAnyPackages(Package pkg, PackageCollection packageCollection) {
        
        ArrayList<Package> usedBy = new ArrayList<Package>();
        
        for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
        {
            Package p = it.next();
            
            for (Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator(); it2.hasNext(); )
            {
                PkgDependency depEntry = it2.next();
                if (  pkg.mAlias.compareTo( depEntry.alias ) == 0  ) {
                    usedBy.add(p);
                    break;
                }
            }
        }
        
        return usedBy;
    }
    
    /**
     *  Locate Daemon Instructions ( TEST and RIPPLE ) Requests that cannot be satisfied - will not be built due to
     *  errors in dependent packages. Report the error to the user and remove the request
     *  
     *  These need o be rejected now as the the Master logic will plan a build a build if there is a daemon instruction
     *  present. If an instruction cannot be processed and is not removed, then the daemon will continuously 'plan'
     */
    private void reportUnsatisfiedDaemonInstructions() throws SQLException, Exception {
        PackageCollection toProcess = new PackageCollection();
        toProcess.addAll(mPackageCollectionTest);
        toProcess.addAll(mPackageCollectionRipple);
    
        for (Iterator<Package> it = toProcess.iterator(); it.hasNext(); )
        {
            Package p = it.next();
    
            //
            //  If the package is in the 'plan, then its not excluded
            //
            boolean isPlanned = false;
//TODO Should mBuildOrder be  aPackageCollection            
            for (Iterator<PlannedPackage> it1 = mBuildOrder.listIterator(); it1.hasNext(); )
            {
                PlannedPackage pkg = it1.next();
                if (pkg.mPkg == p)
                {
                    isPlanned = true;
                    break;
                }
            }
    
            if (! isPlanned) 
            {
    
                // If this package has been excluded, then we can't build it
                //
                boolean excluded = false;
                for (ListIterator<BuildExclusion> it1 = mBuildExclusionCollection.listIterator(); it1.hasNext(); )
                {
                    BuildExclusion be = it1.next();
                    if (be.compare(p.mId))
                    {
                        excluded = true;
                        if (p.mBuildFile >= 0)
                            p.mBuildFile = -5;
                        break;
                    }
                }
    
    
                if (excluded)
                {
                    String reason;
                    switch (p.mBuildFile)
                    {
                    case -1:  reason = "Not reproducible"; break;
                    case -2:  reason = "Not reproducible on configured build platforms"; break;
                    case -3:  reason = "Marked as 'Do not ripple'"; break;
                    case -4:  reason = "Dependent on a package not in the release"; break;
                    case -5:  reason = "Indirectly dependent on a package not reproducible in the release"; break;
                    case -6:  reason = "Has a circular dependency"; break;
                    case -7:  reason = "Pegged or SDK package not in dpkg_archive"; break;
                    case -8:  reason = "Is a Pegged or SDK package"; break;
                    case -9:  reason = "Rejected Daemon Instruction"; break;
                    case -10: reason = "Unbuildable package not in dpkg_archive"; break;
                    case -11: reason = "Marked as 'RippleStop'"; break;
                    case -12: reason = "Cannot generate next version number"; break;
                    default:  reason = "Unknown reason. Code:" + p.mBuildFile; break;
                    }
                    mLogger.warn("planRelease Test Build of an unbuildable of package deleted: {}", p);
    
                    int daemonInstruction = p.mTestBuildInstruction > 0  ? p.mTestBuildInstruction : p.mForcedRippleInstruction; 
                    if ( daemonInstruction <= 0)
                    {
                        mLogger.warn("Daemon Instruction number not found for {}", p);
                    }
    
                    mReleaseManager.markDaemonInstCompleted( daemonInstruction );
                    emailRejectedDaemonInstruction(reason,p);
                }
            }
        }
    
    }

    /** Determine if a given PVID is a member of the current release.
     *  Used to determine if a package dependency is out of date
     * 
     * @param dpvId
     * @return true - specified pvid is a full member of the Release
     */
    private boolean isInRelease(Integer dpvId) {
        
        boolean inRelease = ( mPackageCollection.contains(dpvId) != null);
        return inRelease;
    }


    /** Reports what change in build exceptions happens as part of planRelease
     * 
     *  There are three types of exceptions
     *      PlanError - These may be removed, if the error was not seen in the last plan
     *      BuildErrors - These we persist
     *      IndirectErrors - Packages that depend on a Plan or Build errors
     */
    public void reportChange() throws SQLException, Exception
    {
        int counter = 0;
        for (Iterator<BuildExclusion> it = mBuildExclusionCollection.iterator(); it.hasNext(); )
        {
            BuildExclusion buildExclusion = it.next();

            //  Skip 'Processed' entries
            //      These will be the result of a PlanError that we have seen again
            //
            if ( buildExclusion.isProcessed() ) {
                continue;
            }

            //  BuildErrors     - Persist ( do nothing) These are handled elsewhere
            //  PackageErrors   - Add new (ie: was not imported)
            //  IndirectErrors  - Add/Remove as detected
            //  
            if (buildExclusion.isABuildError()) {
                continue;
                
            } else if (buildExclusion.isAPackageError()) {
                if (buildExclusion.isImported()) {
                    continue;
                }
            }
            
            if (buildExclusion.isImported()) {
                // Remove from the exclusion list
                mLogger.warn("reportChange remove unused exclusion: {}", buildExclusion );
                buildExclusion.includeToBuild(mReleaseManager, mBaseline);

            } else {
                // Exclude and notify
                mLogger.warn("reportChange add new exclusion: {}", buildExclusion );
                buildExclusion.excludeFromBuild(mReleaseManager, mBaseline);
                buildExclusion.email(this, mPackageCollection);
                counter++;
            }
        }
        mLogger.warn("reportChange exclusion count: {}", counter);
    }

    /**reports the build plan
     */
    public void reportPlan() throws SQLException, Exception
    {
        mReleaseManager.reportPlan(mRtagId, mBuildOrder);
    }

    /**Returns the first build file from the collection
     * The build file will be flagged as empty if none exists
     */
    public BuildFile getFirstBuildFileContent()
    {
        mLogger.debug("getFirstBuildFileContent");

        mBuildIndex = -1;
        return getNextBuildFileContent();
    }

    /**Returns next build file from the collection
     * The build file will be flagged as empty if none exists
     */
    public BuildFile getNextBuildFileContent()
    {
        mLogger.debug("getNextBuildFileContent");
        BuildFile retVal = null;

        try
        {
            mBuildIndex++;
            retVal = mBuildCollection.get( mBuildIndex );
        }
        catch( IndexOutOfBoundsException e )
        {
            // Ignore exception. retVal is still null.
        }

        if (retVal == null)
        {
            retVal = new BuildFile();
        }

        mLogger.debug("getNextBuildFileContent returned {}", retVal.state.name() );
        return retVal;
    }


    /**collects meta data associated with the baseline
     * this is sufficient to send an indefinite pause email notification 
     *  
     * Escrow: Used once to collect information about the SBOM and associated Release 
     *         mBaseline is an SBOMID 
     * Daemon: Used each build cycle to refresh the information 
     *          mBaseline is an RTAGID
     */
    public void collectMetaData() throws SQLException, Exception
    {
        Phase phase = new Phase("cmd");
        mLogger.debug("collectMetaData mDaemon {}", mDaemon);

        try
        {
            phase.setPhase("connect");
            mReleaseManager.connect();

            if (! mDaemon)
            {
                mSbomId = mBaseline;
                phase.setPhase("queryRtagIdForSbom");
                mRtagId = mReleaseManager.queryRtagIdForSbom(mBaseline);
                if (mRtagId == 0)
                {
                    mLogger.error("SBOM does not have a matching Release. Cannot be used as a base for an Escrow"); 
                    throw new Exception("rtagIdForSbom show stopper. SBOM does not have an associated Release");
                }
            }

            phase.setPhase("queryReleaseConfig");
            mReleaseManager.queryReleaseConfig(mRtagId);

            if (mDaemon)
            {
                phase.setPhase("queryMailServer");
                setMailServer(mReleaseManager.queryMailServer());
                phase.setPhase("queryMailSender");
                setMailSender(mReleaseManager.queryMailSender());
                phase.setPhase("queryGlobalAddresses");
                setMailGlobalTarget(mReleaseManager.queryGlobalAddresses());
                phase.setPhase("queryProjectEmail");
                mMailGlobalCollection = mReleaseManager.queryProjectEmail(mBaseline);
                phase.setPhase("mMailGlobalTarget");
                mMailGlobalCollection.add(0,getMailGlobalTarget());
                phase.setPhase("mPlanControl");
                mReleaseManager.queryPlanControl(mBaseline, mPlanControl);
            }
            phase.setPhase("queryBaselineName");
            mBaselineName = mReleaseManager.queryBaselineName(mBaseline);
            phase.setPhase("Done");
        }
        finally
        {
            // this block is executed regardless of what happens in the try block
            // even if an exception is thrown
            // ensure disconnect
            mReleaseManager.disconnect();
        }
    }

    /**
     * Sets the mBuildFile to the specified value for the package
     * Does not handle dependent packages - this will be done later  
     *  
     * @param p             The package being excluded 
     * @param rootPvId      The PVID of the package that is causing the exclusion. Null or -ve values are special
     *                      This package is the root cause, -2: Excluded by Ripple Stop 
     * @param rootCause     Text message. Max 50 characters imposed by RM database 
     * @param reason        New value for mBuildFile
     */
    private void rippleBuildExclude(Package p, int rootPvId, String rootCause, int reason )
    {
        mLogger.debug("rippleBuildExclude");
     
        if ( p.mBuildFile >= 0 )
        {
            p.mBuildFile = reason;
            mLogger.info("rippleBuildExclude set mBuildFile to {} for package {}", reason, p.mAlias );
            
            //  Scan the complete collection looking for a matching item
            //  If found then assume that this error is a PlanError that is still present
            //      Mark it as Processed to indicate that its still present
            //  If found, process it, else add it (unprocessed)
            boolean found = false;
            for (Iterator<BuildExclusion> it = mBuildExclusionCollection.iterator(); it.hasNext(); )
            {
                BuildExclusion buildExclusion = it.next();

                if ( buildExclusion.compare(p.mId, rootPvId, rootCause))
                {
                    buildExclusion.setProcessed();
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                // Entry not found in the mBuildExclusionCollection. Its a new error
                // 
                // Mark all occurrences for this package as processed
                // These will be superseded by a new build exclusion entry
                for (Iterator<BuildExclusion> it = mBuildExclusionCollection.iterator(); it.hasNext(); )
                {
                    BuildExclusion buildExclusion = it.next();

                    if ( buildExclusion.compare(p.mId))
                    {
                        buildExclusion.setProcessed();
                    }
                }

                //  Add the new build exclusion to a list
                BuildExclusion buildExclusion = new BuildExclusion(p.mId, rootPvId, rootCause, p.mTestBuildInstruction);
                mBuildExclusionCollection.add(buildExclusion);
            }
        }

        mLogger.info("rippleBuildExclude set {} {}", p.mAlias, p.mBuildFile);
    }

    /**Simple XML string escaping
     * 
     * @param xml               - String to escape
     * @return          - A copy of the string with XML-unfriendly characters escaped 
     */
    public static String escapeXml( String xml )
    {
        xml = xml.replaceAll("&", "&amp;");
        xml = xml.replaceAll("<", "&lt;");
        xml = xml.replaceAll(">", "&gt;");
        xml = xml.replaceAll("\"","&quot;");
        xml = xml.replaceAll("'", "&apos;");
        xml = xml.replaceAll("\\$", "\\$\\$");

        return xml;
    }

    /** Quote a string or a string pair
     *  If two strings are provided, then they will be joined with a comma.
     *   
     * @param text              - First string to quote
     * @param text2             - Optional, second string
     * @return A string of the form 'text','text2'
     */
    public static String quoteString(String text, String text2)
    {
        String result;
        result =  "\'" + text + "\'";
        if (text2 != null )
        {
            result +=  ",\'" + text2 + "\'";  
        }
        return result;
    }

    /** Generate build file information
     * 
     */
    private void generateBuildFiles() 
    {

        // persist the build files
        boolean allProcessed = false;
        int buildFile = 1;
        StringBuilder rawData = new StringBuilder();
        StringBuilder setUp = new StringBuilder();

        mLogger.debug("generateBuildFiles");

        if ( mDaemon )
        {
            // all interesting packages in daemon mode match the following filter
            buildFile = 3;
        }

        //-----------------------------------------------------------------------
        //    Generate the build file
        do
        {
            BuildFile buildEntry = new  BuildFile();
            buildEntry.state = BuildFileState.Dummy;
            XmlBuilder xml = generateBuildFileHeader();


            //  Generate properties for each package in this build level or lower build levels
            //  The properties link the packageAlias to the PackageName and PackageVersion
            //
            //  [DEVI 54816] In escrow mode all unreproducible packages are included 
            for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( ( ( p.mBuildFile > 0 ) && ( p.mBuildFile <= buildFile ) ) || ( !mDaemon && p.mBuildFile == -2 ) )
                {
                    xml.addProperty(p.mAlias, p.mName + " " + p.mVersion + p.mExtension);
                }
            }

            //  UTF Support
            //  Insert additional info into the build file to provide extra checking
            //
            if ( ! mReleaseManager.mUseDatabase )
            {
                // UTF Support
                // Insert per-package planning information
                //
                xml.addComment("mPackageCollection");
                for (Iterator<Package> it = mPackageCollection.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    generatePackageInfo(xml, p);
                }

                xml.addComment("mPackageCollectionWip");
                for (Iterator<Package> it = mPackageCollectionWip.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    if (p.mIsNotReleased )
                        generatePackageInfo(xml, p);
                }
                
                xml.addComment("mPackageCollectionTest");
                for (Iterator<Package> it = mPackageCollectionTest.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    if (p.mIsNotReleased )
                        generatePackageInfo(xml, p);
                }

                xml.addComment("mPackageCollectionTestRipple");
                for (Iterator<Package> it = mPackageCollectionRipple.iterator(); it.hasNext(); )
                {
                    Package p = it.next();
                    if (p.mIsNotReleased )
                        generatePackageInfo(xml, p);
                }

                // UTF Support
                // Insert build exclusion information
                xml.addComment("mBuildExclusionCollection");
                for (Iterator<BuildExclusion> it = mBuildExclusionCollection.iterator(); it.hasNext(); )
                {
                    BuildExclusion buildExclusion = it.next();
                    xml.addComment(buildExclusion.toString());
                }
            }
            
            // UTF Support (Also while trialing the changes)
            // Insert build Plan
            if (mDaemon)
            {
                xml.addComment("mBuildOrder");
                for (Iterator<PlannedPackage> it = mBuildOrder.iterator(); it.hasNext(); )
                {
                    PlannedPackage p = it.next();
                    String comment =
                            "pvid="+ p.mPkg.mId +
                            " order=" + p.mBuildLevel +
                            " time=" + p.mPkg.mBuildTime +
                            " name=\"" + p.mPkg.mAlias + "\"";
                    xml.addComment(comment);
                }
            }

            //  Generate Taskdef information
            generateTaskdef(xml);

            //
            //  Insert known Machine Information
            //  Escrow usage: 
            //      Map machType to machClass
            //  Also serves as a snapshot of the required build configuration
            //  ie: machine types and buildfilters
            //
            if (!mDaemon)
            {
                generateMachineInfo(xml, null);
            }

            //
            //  Generate target rules for each package to be built within the current build file
            //
            boolean daemonHasTarget = false;

            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile > 0 && p.mBuildFile <= buildFile )
                {
                    generateTarget(xml, buildEntry, p, buildFile);

                    if ( p.mBuildFile == 1 )
                    {
                        daemonHasTarget = true;
                        
                        // Retain information about the target package
                        buildEntry.mPkgId = p.mPid;
                        buildEntry.mPvId = p.mId;
                    }
                }

                //      Generate escrow extraction commands
                //      Only done on the first pass though the packages
                //
                if ( !mDaemon && buildFile == 1 )
                {
                    setUp.append("jats jats_vcsrelease -extractfiles"
                                + " \"-label=" + p.mVcsTag + "\""
                                + " \"-view=" + p.mAlias + "\""
                                + " -root=. -noprefix"
                                + mlf );
                }

                //      Generate escrow raw CSV data
                //      Note: I don't think this data is used at all

                if ( !mDaemon && ( p.mBuildFile == buildFile))
                {
                    StringAppender machines = new StringAppender(",");
                    for (Iterator<BuildStandard> it1 = p.mBuildStandardCollection.iterator(); it1.hasNext();)
                    {
                        BuildStandard bs = it1.next();
                        machines.append(bs.mMachClass);
                    }

                    rawData.append(p.mAlias + "," +
                                    buildFile + "," +
                                    machines +
                                    mlf);
                }
            }

            if ( mDaemon && !daemonHasTarget )
            {
                // must have AbtTestPath, AbtSetUp, AbtTearDown, and AbtPublish targets
                XmlBuilder target = xml.addNewElement("target");
                target.addAttribute("name", "AbtTestPath");

                target = xml.addNewElement("target");
                target.addAttribute("name", "AbtSetUp");

                target = xml.addNewElement("target");
                target.addAttribute("name", "AbtTearDown");

                target = xml.addNewElement("target");
                target.addAttribute("name", "AbtPublish");
            }

            generateDefaultTarget( xml, buildFile);

            //  Convert the Xml structure into text and save it in the build file
            //  Add this build file to the mBuildCollection
            buildEntry.content = mXmlHeader + xml.toString();
            mBuildCollection.add(buildEntry);

            // are more build files required
            allProcessed = true;

            if (!mDaemon)
            {
                // this is escrow mode centric
                for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
                {
                    Package p = it.next();

                    if ( p.mBuildFile > buildFile )
                    {
                        // more build files are required
                        allProcessed = false;
                        mLogger.info("planRelease reiterating package has no build requirement {} {} {}",p.mName, p.mBuildFile,  buildFile);
                        break;
                    }
                } 

                buildFile++;
            }

        } while( !allProcessed );

        //      Save additional escrow data
        if ( !mDaemon )
        {
            mEscrowSetup = setUp.toString();
            mEscrowRawData = rawData.toString();
        }
    }

    /**returns a build file header for the mBaseline
     */
    private XmlBuilder generateBuildFileHeader()
    {
        mLogger.debug("generateBuildFileHeader");
        XmlBuilder element = new XmlBuilder("project");

        element.addAttribute("name", "mass");
        element.addAttribute("default", "full");
        element.addAttribute("basedir", ".");

        if ( mDaemon )
        {
            element.addProperty("abt_mail_server", getMailServer());
            element.addProperty("abt_mail_sender", getMailSender()); 
            element.addProperty("abt_rtag_id", mBaseline);
            element.addProperty("abt_daemon", mReleaseManager.currentTimeMillis());
            element.makePropertyTag("abt_packagetarball", true);
            element.makePropertyTag("abt_usetestarchive", !ReleaseManager.getUseMutex());

            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile == 1 )
                {
                    element.addProperty("abt_package_name", p.mName);
                    element.addProperty("abt_package_version", p.mVersion + p.mExtension);
                    element.addProperty("abt_package_extension", p.mExtension);
                    element.addProperty("abt_package_location", getBuildLocation(p));
                    element.addProperty("abt_package_ownerlist", p.emailInfoNonAntTask(this));
                    element.addProperty("abt_package_build_info", buildInfoText(p));

                    // depends in the form 'cs','25.1.0000.cr';'Dinkumware_STL','1.0.0.cots'
                    StringAppender depends = new StringAppender(";");

                    for (Iterator<PkgDependency> it3 = p.mDependencyCollection.iterator(); it3.hasNext(); )
                    {
                        PkgDependency depEntry = it3.next();
                        depends.append( quoteString(depEntry.pkg.mName, depEntry.pkg.mVersion + depEntry.pkg.mExtension) );
                    }

                    element.addProperty("abt_package_depends", depends.toString());
                    element.addProperty("abt_is_ripple", p.mDirectlyPlanned ? "0" : "1");
                    element.addProperty("abt_build_reason", p.mBuildReason.toString());
                    element.addProperty("abt_package_version_id", p.mId);
                    element.addProperty("abt_does_not_require_source_control_interaction", ! p.mRequiresSourceControlInteraction ? "true" : "false" );
                    element.addProperty("abt_test_build_instruction", p.mTestBuildInstruction);
                    element.addProperty("abt_vcs_tag", p.mVcsTag);
                }
            }
        }
        else
        {
            //    Escrow Mode
            element.addProperty("abt_rtag_id", mRtagId);
            element.addProperty("abt_sbom_id", mSbomId);
        }

        element.addProperty("abt_release", escapeXml(mBaselineName));
        element.addProperty("abt_buildtool_version", mReleaseManager.getMajorVersionNumber() );

        element.addNewElement("condition")
        .addAttribute("property", "abt_family")
        .addAttribute("value", "windows")
        .addNewElement("os")
        .addAttribute("family", "windows");
        element.addProperty("abt_family", "unix");

        return element;
    }

    /** Add task def XML items taskdef for the abt ant task
     * @param xml       - xml item to extend
     */
    private void generateTaskdef(XmlBuilder xml)
    {
        mLogger.debug("generateTaskdef");
        xml.addNewElement("taskdef")
        .addAttribute("name", "abt")
        .addAttribute("classname", "com.erggroup.buildtool.abt.ABT");

        xml.addNewElement("taskdef")
        .addAttribute("name", "abtdata")
        .addAttribute("classname", "com.erggroup.buildtool.abt.ABTData");
    }

    /** returns the command abtdata items
     *  <br>Common machine information
     *  <br>Common email address
     *  
     *  
     *  @param  xml - Xml element to extend   
     *  @param    p - Package (May be null)
     */
    private void generateMachineInfo(XmlBuilder xml, Package p)
    {
        XmlBuilder element = xml.addNewElement("abtdata");
        element.addAttribute("id", "global-abt-data");

        //
        //  Iterate over all the machines and create a nice entry
        //
        for (Iterator<ReleaseConfig> it = mReleaseManager.mReleaseConfigCollection.mReleaseConfig.iterator(); it.hasNext(); )
        {
            ReleaseConfig rc = it.next();
            element.addElement(rc.getMachineEntry());
        }

        //
        //  Now the email information
        //
        if ( p != null)
        {
            p.emailInfo(this, element);
        }
    }

    /** Generate package information
     *  Only when running unit tests
     *  
     * @param xml       - An xml element to append data to
     * @param p - Package to process
     */
    private void generatePackageInfo (XmlBuilder xml, Package p)
    {
        StringBuilder comment = new StringBuilder();
        StringBuilder deps = new StringBuilder();
        
        String joiner = "";
        for (Iterator<PkgDependency> it2 = p.mDependencyCollection.iterator(); it2.hasNext(); )
        {
            PkgDependency depEntry = it2.next();
            deps.append(joiner).append(depEntry.alias); 
            joiner = ",";
        }
        
        comment.append("pvid=").append(p.mId);
        comment.append(" name=").append('"').append(p.mAlias).append('"');
        comment.append(" reason=").append(p.mNoBuildReason);
        comment.append(" buildFile=").append(p.mBuildFile);
        comment.append(" directlyPlanned=").append(p.mDirectlyPlanned);
        comment.append(" indirectlyPlanned=").append(p.mIndirectlyPlanned);
        comment.append(" depends=[").append(deps).append("]");

        xml.addComment(comment.toString());            
    }

    /**returns an ant target for the passed Package
     * in daemon mode:
     *  packages are categorized with one of three mBuildFile values:
     *   1 the package to be built by this buildfile
     *   2 the packages with a future build requirement
     *   3 the packages with no build requirement
     *  the returned target depends on this categorization and will have
     *   1 full abt info
     *   2 full dependency info to determine future build ordering but no abt info (will not build this package)
     *   3 only a name attribute (will not build this package)
     * in escrow mode:
     *  if the passed Package's mBuildFile is different (less than) the passed build file,
     *  the returned target have only a name attribute (will not build this package)
     *   
     * @param xml - Xml element to extend
     * @param buildEntry - Record build type (dummy/generic/not generic)
     * @param p - Package to process
     * @param buildFile - buildfile level being processed
     */
    private void generateTarget(XmlBuilder xml, BuildFile buildEntry, Package p, int buildFile)
    {
        mLogger.debug("generateTarget");

        //---------------------------------------------------------------------
        //  Generate the AbtData - common machine and email information
        //  Only used by the daemon builds
        //
        if ( mDaemon && p.mBuildFile == 1 )
        {
            generateMachineInfo(xml, p );
        }

        //-------------------------------------------------------------------------
        //  Generate the <target name=... /> construct
        //  There are two types
        //      1) Simple dummy place holder. Just has the PackageName.PackageExt
        //      2) Full target. Has all information to build the package including
        //              AbtSetUp, AbtTearDown and AbtPublish targets
        //

        if ( !mDaemon && ( p.mBuildFile < buildFile ) )
        {
            XmlBuilder target = xml.addNewElement("target");
            target.addAttribute("name", p.mAlias);
        }
        else
        {
            if (!mDaemon) 
            {
                //  Escrow Only:
                //  Generate the 'wrapper' target
                //  This is used to ensure that the required dependencies have been built - I think
                //
                StringAppender dependList = new StringAppender(",");
                if ( !p.mDependencyCollection.isEmpty() )
                {
                    for (Iterator<PkgDependency> it = p.mDependencyCollection.iterator(); it.hasNext(); )
                    {
                        PkgDependency depEntry = it.next();
                        if ( !mDaemon && depEntry.pkg.mBuildFile == -2 )
                        {
                            // ignore targets which build in foreign environments in escrow mode
                            continue;
                        }
                        dependList.append(depEntry.pkg.mAlias);
                    }
                }
    
                XmlBuilder target = xml.addNewElement("target").isExpanded();
                target.addAttribute("name", p.mAlias + ".wrap");
    
                if (dependList.length() > 0)
                {
                    target.addAttribute("depends", dependList.toString() );
                }
    
                if ( !mDaemon )
                {
                    boolean hasDependenciesBuiltInThisIteration = false;
                    if ( ( !p.mDependencyCollection.isEmpty()) )
                    {
                        for (Iterator<PkgDependency> it = p.mDependencyCollection.iterator(); it.hasNext(); )
                        {
                            PkgDependency depEntry = it.next();
    
                            if ( depEntry.pkg.mBuildFile == buildFile )
                            {
                                hasDependenciesBuiltInThisIteration = true;
                                break;
                            }
                        }
                    }
    
                    if ( hasDependenciesBuiltInThisIteration )
                    {
                        XmlBuilder condition = target.addNewElement("condition");
                        condition.addAttribute("property",  p.mAlias + ".build");
    
                        XmlBuilder and = condition.addNewElement("and");
    
                        for (Iterator<PkgDependency> it = p.mDependencyCollection.iterator(); it.hasNext(); )
                        {
                            PkgDependency depEntry = it.next();
    
                            if ( depEntry.pkg.mBuildFile == buildFile )
                            {
                                XmlBuilder or = and.addNewElement("or");
    
                                XmlBuilder equal1 = or.addNewElement("equals");
                                equal1.addAttribute("arg1", "${" + depEntry.pkg.mAlias + ".res}");
                                equal1.addAttribute("arg2", "0");
    
                                XmlBuilder equal2 = or.addNewElement("equals");
                                equal2.addAttribute("arg1", "${" + depEntry.pkg.mAlias + ".res}");
                                equal2.addAttribute("arg2", "257");
                            }
                        }
                    }
                    else
                    {
                        target.addProperty(p.mAlias + ".build", "");
                    }
                }
            }


            //
            //  Generate the 'body' of the target package
            //  Escrow Mode: Always
            //  Daemon Mode: Only for the one target we are building
            //                  Simplifies the XML
            //                  Reduces noise in the logs
            //              Don't add target dependencies. 
            //                  We are only building one target and the 
            //                  dependency management has been done way before now.
            //                  All it does is makes the logs noisy.
            //
            if ( ( mDaemon && p.mBuildFile == 1 ) || !mDaemon )
            {
                XmlBuilder target = xml.addNewElement("target").isExpanded();
                target.addAttribute("name", p.mAlias);
    
                if ( !mDaemon )
                {
                    target.addAttribute("depends",  p.mAlias + ".wrap");
                    target.addAttribute("if",p.mAlias + ".build");
                }
    
                if ( mDaemon && p.mBuildFile == 1 )
                {
                    target.addProperty(p.mAlias + "pkg_id",p.mPid);
                    target.addProperty(p.mAlias + "pv_id",p.mId);
                }
    
                target.addProperty(p.mAlias + "packagename",p.mName);                   
                target.addProperty(p.mAlias + "packageversion",p.mVersion);
                target.addProperty(p.mAlias + "packageextension",p.mExtension);
                target.addProperty(p.mAlias + "packagevcstag",p.mVcsTag);

                target.makePropertyTag(p.mAlias + "directchange", p.mDirectlyPlanned); 
                target.makePropertyTag(p.mAlias + "doesnotrequiresourcecontrolinteraction", ! p.mRequiresSourceControlInteraction);

                buildEntry.state = BuildFile.BuildFileState.NonGeneric;
                if ( p.isGeneric() )
                {
                    buildEntry.state = BuildFile.BuildFileState.Generic;
                    target.makePropertyTag(p.mAlias + "generic", true); 
                }

                target.addProperty(p.mAlias + "loc", getBuildLocation(p));
                target.makePropertyTag(p.mAlias + "unittests", p.mHasAutomatedUnitTests && mDaemon);

                //    Add our own task and associated information
                //
                XmlBuilder abt = target.addNewElement("abt").isExpanded();
    
                for (Iterator<PkgDependency> it = p.mDependencyCollection.iterator(); it.hasNext(); )
                {
                    PkgDependency depEntry = it.next();
                    XmlBuilder depend = abt.addNewElement("depend");
                    depend.addAttribute("package_alias", "${" + depEntry.pkg.mAlias + "}");
                }

                buildInfo(abt,p);
    
                if ( mDaemon && p.mBuildFile == 1 )
                {
                    //    AbtTestPath
                    target = xml.addNewElement("target").isExpanded();
                    target.addAttribute("name", "AbtTestPath");
                    target.addProperty("AbtTestPathpackagevcstag", p.mVcsTag);
                    target.addProperty("AbtTestPathpackagename", p.mName);
                    abt = target.addNewElement("abt").isExpanded();
                    buildInfo(abt, p);
 
                    
                    //    AbtSetUp
                    target = xml.addNewElement("target").isExpanded();
                    target.addAttribute("name", "AbtSetUp");
                    target.addProperty("AbtSetUppackagevcstag", p.mVcsTag);
                    target.addProperty("AbtSetUppackagename", p.mName);
    
                    abt = target.addNewElement("abt").isExpanded();
                    buildInfo(abt, p);

                    //  AbtBuildInfo
                    target = xml.addNewElement("target").isExpanded();
                    target.addAttribute("name", "AbtBuildInfo");
                    target.addProperty("AbtBuildInfo"+"packagevcstag", p.mVcsTag);
                    target.addProperty("AbtBuildInfo"+"packagename", p.mName);
                    target.addProperty("AbtBuildInfo"+"packageversion", p.mVersion);
                    target.addProperty("AbtBuildInfo"+"packageextension", p.mExtension);
                    target.makePropertyTag("AbtBuildInfo"+"generic", p.isGeneric());
                    target.addProperty("AbtBuildInfo"+"loc", getBuildLocation(p));
    
                    abt = target.addNewElement("abt").isExpanded();
                    buildInfo(abt, p);

                    //    AbtTearDown
                    target = xml.addNewElement("target").isExpanded();
                    target.addAttribute("name", "AbtTearDown");
                    target.addProperty("AbtTearDownpackagevcstag", p.mVcsTag);
                    target.addProperty("AbtTearDownpackagename", p.mName);
                    target.addProperty("AbtTearDownpackageversion", p.mVersion);
                    target.addProperty("AbtTearDownpackageextension", p.mExtension);
                    target.makePropertyTag(p.mAlias + "generic", p.isGeneric());
    
                    abt = target.addNewElement("abt").isExpanded();
                    buildInfo(abt, p);
    
    
                    //  AbtPublish
                    target = xml.addNewElement("target").isExpanded();
                    target.addAttribute("name", "AbtPublish");
    
                    target.addProperty("AbtPublishpackagevcstag", p.mVcsTag);
                    target.addProperty("AbtPublishpackagename", p.mName);
                    target.addProperty("AbtPublishpackageversion", p.mVersion);
                    target.addProperty("AbtPublishpackageextension", p.mExtension);
                    target.makePropertyTag("AbtPublishdirectchange", p.mDirectlyPlanned);
                    target.makePropertyTag("AbtPublishdoesnotrequiresourcecontrolinteraction", ! p.mRequiresSourceControlInteraction);
                    target.makePropertyTag("AbtPublishgeneric", p.isGeneric());
                    target.addProperty("AbtPublishloc", getBuildLocation(p));
    
                    abt = target.addNewElement("abt").isExpanded();
                    buildInfo(abt, p);
    
                }
            }
        }
    }

    /** Extends the xml object. Adds ant default target for the current build iteration
     * @param xml - The XmlBuilder Object to extend
     * @param buildFile - The current build file level. This differs for Daemon and Escrow mode. In Daemon mode it will not be a '1' 
     */
    private void generateDefaultTarget(XmlBuilder xml, int buildFile)
    {
        mLogger.debug("generateDefaultTarget");

        XmlBuilder target = xml.addNewElement("target").isExpanded();
        target.addAttribute("name", "fullstart");

        if (buildFile == 1)
        {
            antEcho(target, "${line.separator}" + mAnyBuildPlatforms + "${line.separator}${line.separator}");
            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile == -1 )
                {
                    antEcho(target, "${line.separator}" + p.mAlias + "${line.separator}");
                }
            }

            antEcho(target, "${line.separator}" + mAssocBuildPlatforms + "${line.separator}${line.separator}");
            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile == -2 )
                {
                    antEcho(target, "${line.separator}" + p.mAlias + "${line.separator}");
                }
            }

            antEcho(target, "${line.separator}" + mNotInBaseline + "${line.separator}${line.separator}");
            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile == -4 )
                {
                    antEcho(target, "${line.separator}" + p.mAlias + "${line.separator}");
                }
            }

            antEcho(target, "${line.separator}" + mDependent + "${line.separator}${line.separator}");
            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package p = it.next();

                if ( p.mBuildFile == -5 )
                {
                    antEcho(target, "${line.separator}" + p.mAlias + "${line.separator}");
                }
            }
        }

        if ( !mDaemon )
        {
            antEcho(target, "${line.separator}Build Started:${line.separator}${line.separator}");
        }

        //
        //      Create a comma separated list of all the required targets
        //      Escrow : All packages
        //      Daemon : Just the package being built
        //
        StringAppender dependList = new StringAppender(",");
        dependList.append("fullstart");
        for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
        {
            Package p = it.next();

            if ( ( p.mBuildFile > 0 ) && ( p.mBuildFile <= buildFile ) )
            {
                if ((mDaemon && p.mBuildFile == 1) || !mDaemon)
                {
                    dependList.append(p.mAlias);                    
                }
            }
        }

        target = xml.addNewElement("target").isExpanded();
        target.addAttribute("name", "full");
        target.addAttribute("depends", dependList.toString());

        if ( !mDaemon )
        {
            antEcho(target, "${line.separator}Build Finished${line.separator}");
        }
    }

    /** Internal helper function to create an ant 'echo statement
     *  Many of the parameters are fixed
     */
    private void antEcho( XmlBuilder xml, String message)
    {
        XmlBuilder msg = xml.addNewElement("echo");
        msg.addAttribute("message", message);
        msg.addAttribute("file", "publish.log");
        msg.addAttribute("append", "true");
    }

    /**sets the mIndirectlyPlanned true for the package and all dependent packages
     */
    private void rippleIndirectlyPlanned(Package p)
    {
        mLogger.debug("rippleIndirectlyPlanned");
        if ( !p.mIndirectlyPlanned && p.mBuildFile == 0 )
        {
            p.mIndirectlyPlanned = true;

            for (Iterator<Package> it = mPackageCollectionAll.iterator(); it.hasNext(); )
            {
                Package pkg = it.next();

                if ( pkg != p )
                {
                    for (Iterator<PkgDependency> it2 = pkg.mDependencyCollection.iterator(); it2.hasNext(); )
                    {
                        PkgDependency depEntry = it2.next();

                        if ( depEntry.pkg == p )
                        {
                            rippleIndirectlyPlanned( pkg );
                            break;
                        }
                    }
                }
            }
        }
        mLogger.info("rippleIndirectlyPlanned set {} {}", p.mName, p.mIndirectlyPlanned);    
    }

    /**accessor method
     */
    public String getEscrowSetUp()
    {
        mLogger.debug("getEscrowSetUp");
        String retVal = mEscrowSetup;

        mLogger.debug("getEscrowSetUp returned {}", retVal);
        return retVal;
    }

    /**accessor method
     */
    public String getRawData()
    {
        mLogger.debug("getRawData");
        String retVal = mEscrowRawData;

        mLogger.debug("getRawData returned {}", retVal);
        return retVal;
    }

    /**Get the build loc (location)
     * This is package specific and will depend on the build mode (Escrow/Daemon)
     * 
     * @param   p - Package being built
     * @return A string that describes the build location
     */
    private String getBuildLocation(Package p)
    {
        mLogger.debug("locationProperty");
        String location = "";

        if (mDaemon)
        {
            // Daemon: Start in root of view/workspace
            location += mBaseline;
        }
        else
        {
            // Escrow: mAlias used with jats -extractfiles -view
            location += p.mAlias;
        }

        //
        //  Always use '/' as a path separator - even if user has specified '\'
        //  Ant can handle it.
        //
        location = location.replace('\\', '/');
        return location;
    }

    /**Adds package build into as XML elements 
     * @param     xml   - Xml element to extend
     * @param   p       - Package to process
     */
    private void buildInfo(XmlBuilder xml, Package p)
    {
        mLogger.debug("buildInfo");

        //
        // Create the xml build information
        // <platform gbe_machtype="linux_i386" type="jats" arg="all"/>
        //
        for (Iterator<BuildStandard> it = p.mBuildStandardCollection.iterator(); it.hasNext();)
        {
            BuildStandard bs = it.next();
            bs.getBuildStandardXml(xml);
        }
    }

    /**returns the buildInfo as a single line of text
     * Used for reporting purposes only
     * @param   p       - Package to process
     * 
     */
    public String buildInfoText(Package p)
    {
        mLogger.debug("buildInfoText");

        StringAppender result = new StringAppender (";");

        //
        //  Create platform:standards
        //      
        for (Iterator<BuildStandard> it = p.mBuildStandardCollection.iterator(); it.hasNext(); )
        {
            BuildStandard bs = it.next();

            if ( bs.isActive() )
            {
                String info = bs.getBuildStandardText();
                result.append(info);
            }
        }

        mLogger.debug("buildInfoText returned {}", result);
        return result.toString();
    }

    /**prints to standard out in escrow mode only
     * <br>Prints a title and information. The title is only printed once.
     * 
     * @param header - The message title to display, if printMessage is true
     * @param text - A package name. Really just the 2nd line of the message
     * @param printHeader -  Controls the printing of the message argument
     */
    private void standardOut(final String header, final String text, boolean printHeader)
    {
        mLogger.debug("standardOut");
        if (!mDaemon)
        {
            if ( printHeader )
            {
                System.out.println(header);
            }

            System.out.println(text);
        }
    }


    /**
     *  Email users about a rejected daemon instruction
     *  @param  reason  - Reason for the rejection
     *  @param  pkg     - Package to be affected by the instruction
     *  
     */
    public void emailRejectedDaemonInstruction(String reason, Package p)
    {
        mLogger.debug("emailRejectedDaemonInstruction");

        //  Email Subject
        String subject = "BUILD FAILURE of Daemon Instruction on package " + p.mAlias;

        // Email Body
        String mailBody = "The build system reject the the Daemon Instruction";
        
        mailBody += "<p>Release: " + mBaselineName 
                 +  "<br>Package: " + p.mAlias
                 +  "<br>Cause : " + reason
                 +  "<br>Rm Ref: " + CreateUrls.generateRmUrl(getRtagId(), p.mId); 

        mailBody += "<p><hr>";

        String target = p.emailInfoNonAntTask(this);

        mLogger.info("emailRejectedDaemonInstruction Server: {}", getMailServer());
        mLogger.info("emailRejectedDaemonInstruction Sender: {}", getMailSender());
        mLogger.warn("emailRejectedDaemonInstruction Target: {}", target);

        try
        {
            //    
            Smtpsend.send(getMailServer(),  // mailServer
                    getMailSender(),        // source
                    target,                 // target
                    getMailSender(),        // cc
                    null,                   // bcc
                    subject,                // subject
                    mailBody,               // body
                    null                    // attachment
                    );
        } catch (Exception e)
        {
            mLogger.warn("Email Failure: emailRejectedDaemonInstruction:{}", e.getMessage());
        }
    }

    /** Send an email notifying users that a rippleStop has been triggered
     *  The use will need to take some action
     * @param p - Package
     */
    private void emailRippleStop(Package p) {
        mLogger.debug("emailRippleStop");

        //  Email Subject
        String subject = "BUILD FAILURE on package " + p.mAlias;
        
        // Failure Reason
        String reason = "Ripple Required. Waiting for user action";

        // Email Body
        String mailBody = "<p>Release: " + mBaselineName 
                       +  "<br>Package: " + p.mAlias
                       +  "<br>Cause : " + reason
                       +  "<br>Rm Ref: " + CreateUrls.generateRmUrl(getRtagId(), p.mId); 

        mailBody += "<p><hr>";

        String target = p.emailInfoNonAntTask(this);

        mLogger.info("emailRippleStop Server: {}", getMailServer());
        mLogger.info("emailRippleStop Sender: {}", getMailSender());
        mLogger.warn("emailRippleStop Target: {}", target);

        try
        {
            //    
            Smtpsend.send(getMailServer(),  // mailServer
                    getMailSender(),        // source
                    target,                 // target
                    getMailSender(),        // cc
                    null,                   // bcc
                    subject,                // subject
                    mailBody,               // body
                    null                    // attachment
                    );
        } catch (Exception e)
        {
            mLogger.warn("Email Failure: emailRippleStop:{}", e.getMessage());
        }
        
    }

    /**
     * @return the mMailServer
     */
    public String getMailServer() {
        return mMailServer;
    }

    /**
     * @param mMailServer the mMailServer to set
     */
    public void setMailServer(String mMailServer) {
        this.mMailServer = mMailServer;
    }

    /**
     * @return the mMailSender
     */
    public String getMailSender() {
        return mMailSender;
    }

    /**
     * @param mMailSender the mMailSender to set
     */
    public void setMailSender(String mMailSender) {
        this.mMailSender = mMailSender;
    }

    /**
     * @return the mMailGlobalTarget
     */
    public String getMailGlobalTarget() {
        return mMailGlobalTarget;
    }

    /**
     * @param mMailGlobalTarget the mMailGlobalTarget to set
     */
    public void setMailGlobalTarget(String mMailGlobalTarget) {
        this.mMailGlobalTarget = mMailGlobalTarget;
    }

}