Subversion Repositories DevTools

Rev

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

package com.erggroup.buildtool.ripple;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Vector;

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

import com.erggroup.buildtool.ripple.ReleaseManager.BuildReason;
import com.erggroup.buildtool.utilities.MutableInt;
import com.erggroup.buildtool.utilities.XmlBuilder;
import com.erggroup.buildtool.utilities.utilities;

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

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

    /**
     * instance identifier
     * 
     * @attribute
     */
    public String mVersion = new String();
    
    /**
     * If this package is a candidate for building, then this value will be calculated
     * and will  be used if the package is selected for building
     */
    public String mNextVersion = null;

    /**
     * Version string as specified by the user. Used with a ripple type of 'F'
     */
    String mFixedVersion = new String();

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

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

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

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

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

    public class PkgDependency {
        String alias;
        Integer pvId;
        Package pkg;
        
        public PkgDependency( String alias, Integer pvId)
        {
            this.alias = alias;
            this.pvId = pvId;
            pkg = null;
        }
       
        //  Assist in debugging
        public String toString()
        {
            String pkgData = "";
            if (pkg != null)
                pkgData = " {" + pkg.mId + ":" + pkg.mAlias +"}";
      
            return Integer.toString(pvId) + ":" + alias  + pkgData;
        }
    }
    
    /**
     * Package dependencies
     * A triplet of alias, pvId and Package
     *  alias and pvId are read from the database
     *  Package is located during planning
     * 
     */
    public Vector<PkgDependency> mDependencyCollection = new Vector<PkgDependency>();
    
    /**
     * indication of the nature of change
     * 
     * @attribute
     */
    Package.VersionNumberingStandard mChangeType = new VersionNumberingStandard();

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

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

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

    /**
     * when true, do not ripple this package through packages which are
     * dependent upon it in daemon mode
     * 
     * @attribute
     */
    public boolean mAdvisoryRipple = false;
    
    /**
     * Pegged packages will:
     *   - Not be rippled
     *   - Not be test buildable
     *   - Dependencies will not be considered
     *   Daemon Mode:
     *      Are not built in this release
     *      Are imported with out consideration as to their dependencies
     *      Must exist in dpkg_archive - will not be built if not present
     *   Escrow Mode:
     *      Have no special consideration
     *      The package and its dependencies will be built
     *
     * Packages imported into the release from an SDK will be treated as if they were
     * pegged by the built system.
     * 
     */
    public boolean mIsPegged = false;
    public boolean mIsSdk = false;
    
    /**
     * Unbuildable packages will not be candidates for a ripple or a rebuild
     * If they do not exist in dpkg_archive, then then will cause an error
     */
    public boolean mIsBuildable = true;

    /**
     * determines the build file the package is built in, or not
     * <br>   1 Post Plan: build file - Result of planning
     * <br>   2 Post Plan: Future build requirement
     * <br>   3 Post Plan: Package has no build requirement
     * <br>   0 not yet processed (initial value) 
     * <br>  -1 not reproducible
     * <br>  -2 escrow: not reproducible on the build platforms configured for this release
     * <br>  -3 daemon: do not ripple
     * <br>  -4 directly dependent on package versions not in the baseline
     * <br>  -5 indirectly dependent on package versions which are not reproducible due to detected fault
     * <br>  -6 circular dependency
     * <br>  -7 Pegged or SDK Imported package not in dpkg_archive
     * <br>  -8 Pegged or SDK Imported package
     * <br>  -9 Rejected Daemon Instruction
     * <br>  -10 UnBuildable Package not in dpkg_archive
     * <br>  -11 Marked as RippleStop
     * <br>  -12 Cannot calc next version number
     * 
     */
    int mBuildFile = 0;
    
    /** Retained reason the package will not be built
     *  Save negative values as mBuildFile
     * @attribute
     */
    int mNoBuildReason = 0;

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

    /**
     * Reason that a package is being built
     */
    public BuildReason mBuildReason = null;
    
    /**
     * set true for WIP package versions only used in daemon mode
     * 
     * @attribute
     */
    public boolean mDirectlyPlanned = false;

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

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

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

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

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

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

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

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

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

    /**
     * dpkg archive location - The writable dpkg_archive
     * 
     * @attribute
     */
    public static String mGbeDpkg = System.getenv("GBE_DPKG");
    
    /**
     *  A physically close replica of dpkg_archive
     *  Is not writable 
     */
    public static final String mGbeDpkgReplica = System.getenv("GBE_DPKG_REPLICA");

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

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

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

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

    /**
     * when true has circular dependency, or in the process of detecting a circular dependency
     * 
     * @attribute
     */
    boolean mHasCircularDependency = false;
    
    /**
     * Bread crumb used to detect circular dependencies
     * 0 - Normal state
     * 1 - Crumb has been set
     * 2 - Crumb is a part of a circular dependency
     * 3 - Crumb is the start/end of the loop
     */
    int mBreadCrumb = 0;

    /**
     * Planned packages (WIPS) need calculate a new version number
     * This should be based on the Version of the package that the WIP was based upon
     */
        public String mPrevVersion;
        
    /** Used to preserve the basic ordering of the package list(s)
     *  Only valid for the duration of the comparison sorts
     */
    int mSeqId; 
        
        /** Indicates that the package not be ripple built at this time
         *  Sequence is:
         *     User sets to 's'
         *     This buildtool will set to 'w' when a ripple has been detected
         *     User resets to NULL when the ripple can be resumed. This is stored as an 'n'
         * 
         */
    public char mRippleStop;
    
    /** The duration (seconds) of the last build of this package
     *  Used to estimate the cost of building the package
     *  A WIP packages gets the buildTime of the package its based upon
     */
    public int mBuildTime;

    /** Set to indicate that the package is NOT a part of the full release set
     *  The package is a WIP, TestBuild or a ForcedRipple
     *  
     *   Used to simplify detection of such packages and to limit processing of these packages
     *   May be cleared if the package is added to the full set
     */
    public boolean mIsNotReleased = false;
    
    /** Used by some algorithms for various indications
     * 
     */
    public boolean mIsProcessed = false;

    /**
     * Constructor
     * Used to create planned packages (WIP). In these packages the change_type is significant
     * 
     * @param pkgId        Package Name Identifier
     * @param pvId        Package Version Id
     * @param pkgName     Package Name
     * @param pkgVersion  Package Version
     * @param vExt        Package Suffix
     * @param alias        Package Alias
     * @param pkgVcsTag  Vcs Tag
     * @param rippleField Ripple Field
     * @param changeType  Change Type
     */
    public Package(int pkgId, int pvId, String pkgName, String pkgVersion, String vExt, String alias, String pkgVcsTag, char rippleField, char changeType)
    {
        mLogger.debug("Package 1: pv_id {} pkg_name {} v_ext {} alias {} pkg_vcs_tag {} ripple_field {} change_type {}", pvId, pkgName,vExt,alias, pkgVcsTag, rippleField, changeType);
        mId = pvId;
        mName = pkgName;
        mPid = pkgId;
        mVersion = "0.0.0000";
        mExtension = vExt;
        mAlias = alias;
        mVcsTag = pkgVcsTag;

        // Remove the package suffix from package_version to create the fixed version number
        mFixedVersion = pkgVersion;
        mFixedVersion = mFixedVersion.substring(0, mFixedVersion.length() - mExtension.length());

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

    /**
     * Constructor
     * Used to create existing packages, in these packages the ripple_field is significant
     * 
     * @param pkgId        Package Name Identifier
     * @param pvId        Package Version Id
     * @param pkgName     Package Name
     * @param pkgVersion  Package Version
     * @param vExt        Package Suffix
     * @param alias        Package Alias
     * @param pkgVcsTag  Vcs Tag
     * @param rippleField Ripple Field
     */
    public Package(int pkgId,int pvId, String pkgName, String pkgVersion, String vExt, String alias, String pkgVcsTag, char rippleField)
    {
        mLogger.debug("Package 2: pv_id {} pkg_name {} v_ext {} alias {} pkg_vcs_tag {} ripple_field {}", pvId, pkgName,vExt,alias, pkgVcsTag, rippleField);
        mId = pvId;
        mName = pkgName;
        mPid = pkgId;
        mVersion = pkgVersion;
        int endindex = mVersion.length() - vExt.length();

        if (endindex > 0)
        {
            mVersion = mVersion.substring(0, endindex);
        }

        mExtension = vExt;
        mAlias = alias;
        mVcsTag = pkgVcsTag;

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

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

    /**
     * Constructor for unit test purposes
     *  Will invoke applyPV and save the results for the UTF framework
     * @param proj Package Suffix ( or Project extension)
     * @param isaTest - True - Generate testRequest
     */
    public Package(ReleaseManager rm, String version, String proj, boolean isaTest, int majorLimit, int minorLimit, int patchLimit, int buildNumberLimit)
    {
        mId = -1;
        mRippleField.setLimit();
        mVersion = version;
        mExtension = proj;
        mMajorLimit = majorLimit;
        mMinorLimit = minorLimit;
        mPatchLimit = patchLimit;
        mBuildLimit = buildNumberLimit;

        if (isaTest)
        {
                mTestBuildInstruction = 77;
        }
        
        if (proj.compareTo(".cots") == 0 || proj.compareTo(".tools") == 0 )
        {
            mChangeType.setMajor(false);
            mChangeType.setMinor(false);
            mChangeType.setPatch(true);
            mRippleField.setBuild();
        }
  
        try
        {
            mId = applyPV(rm);
        } catch (Exception e)
        {
        }
    }

    /**
     * Constructor for unit test purposes
     * Performs a partial copy of a package - sufficient for test purposes
     * @param base      - Base package  
     * @param newPvId   - New pvid of the package
     */

    public Package(int newPvId, Package base) {
        
        mId = newPvId;
        mPid = base.mPid;
        mName = base.mName;
        mExtension = base.mExtension;
        mVersion = base.mVersion;
        mAlias = base.mAlias;
        mVcsTag = base.mVcsTag;
        mFixedVersion = base.mFixedVersion;
        mChangeType = base.mChangeType;
        mRippleField = base.mRippleField;
        mBuildTime = base.mBuildTime;
        mHasAutomatedUnitTests = base.mHasAutomatedUnitTests;
        mSeqId = base.mSeqId;
        
        mBuildFailureEmailCollection = new Vector<String>(base.mBuildFailureEmailCollection);
        mDependencyCollection = new Vector<PkgDependency>(base.mDependencyCollection);
        mBuildStandardCollection = new Vector<BuildStandard>( base.mBuildStandardCollection);
    }
    
    /** Generate a nice text string useful for debugging
     * 
     */
    public String toString()
    {
        return mId + ":" + mAlias;
    }

    /**
     * accessor for unit test purposes
     */
    public int getId()
    {
        return mId;
    }

    /**
     * accessor for unit test purposes
     */
    public String getVersion()
    {
        return mVersion;
    }
    
    /**
     * accessor for unit test purposes
     */
    public String getNextVersion()
    {
        return mNextVersion;
    }
    

    /**
     * returns true if mBuildStandardCollection is not empty
     */
    boolean isReproducible()
    {
        mLogger.debug("isReproducible on Package {}", mName);
        boolean retVal = ! mBuildStandardCollection.isEmpty();
        mLogger.debug("isReproducible returned {}", retVal);
        return retVal;
    }


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

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

        mLogger.debug("isGeneric returned {}", retVal);
        return retVal;
    }
    
    /**
     * Returns true if at least one of the packages build standards can be 
     * built in the named machine class. 
     *  
     * Used to determine if the package can be built with the current buildset 
     * by iteration over all machine classes in the buildset. 
     */
    boolean canBeBuildby(String machineClass)
    {
        mLogger.debug("canBeBuildby on Package " + mName);
        boolean retVal = false;
        for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();)
        {
            BuildStandard buildStandard = it.next();

            if (buildStandard.isGeneric())
            {
                retVal = true;
                break;
            }
            
            if (buildStandard.mMachClass.equals(machineClass))
            {
                retVal = true;
                break;
            }
        }

        mLogger.info("canBeBuildby returned " + retVal);
        return retVal;
    }
    
    /**
     * Compare the build standards of two packages 
     * Used only in escrow mode 
     * Used to compare a package and its dependents (one by one) 
     *  
     * Returns true if the parent (this) package  can be built in the same escrow 
     * build iteration as the dependent package. 
     *  
     * This is a complex decision and has a few built in assumptions. 
     *  
     * If the dependent package is 'generic' then the parent package can be built 
     *  
     * If the parent package is generic then is can only be built if the dependent is 
     * also generic. Otherwise we must assume that the parent package is an agregator 
     * package and requires artifacts from the dependent package built by all of its 
     * required build machines. 
     *  
     * If both packages are not generic, then if the build standards of the parent and the 
     * dependent are identical then 
     *  
     *      If we have one build standard, we can build the parent in this iteration as
     *      the dependent package has been completely built.
     *  
     *      If we have more than one build standard ( but they are identical ) then we
     *      ASSUME that we can build the parent in this iteration. The assumption is that
     *      there is no mixing between build machines. ie: Windows consumer users Windows
     *      artifacts and the Linux consumer uses Linux artifacts ...
     *  
     * If both packages are not generic and the build standards are not identical, then 
     * things get hard. The safest solution is to assume the parent cannot be built in this 
     * iteration. This is not a bad assumption. 
     *  
     */
    boolean haveSameBuildStandards(Package d)
    {
        HashMap<String, Boolean> standardSet = new HashMap<String, Boolean>();
        boolean isGeneric = false;
        boolean isGeneric_d = false;
        boolean isIdentical = true;

        // Scan the build standards of the parent package and create a hash map for each machine
        // class required by the parent. Also determine if the parent is generic.
        for (Iterator<BuildStandard> it = mBuildStandardCollection.iterator(); it.hasNext();)
        {
            BuildStandard bs = it.next();
            standardSet.put(bs.mMachClass, false);
            
            if (bs.isGeneric())
            {
                isGeneric = true;
                break;
            }
        }

        // Scan the build standards in the dependent package and remove items from the map
        // If it was not in the map then the dependent package builds for platforms that the
        // parent package does not - thus the build standards cannot be identical
        // Also determine if the dependent is generic.
        for (Iterator<BuildStandard> it = d.mBuildStandardCollection.iterator(); it.hasNext();)
        {
            BuildStandard bs = it.next();
             if (bs.isGeneric())
             {
             isGeneric_d = true;
             break;
             }

            Boolean value = standardSet.remove(bs.mMachClass);
            if (value == null)
            {
                isIdentical = false;
                break;
            }
        }

        //
        // If there are any items left in the map then the parent package builds on machines
        // that the dependent does not. The two are not identical.

        if (!standardSet.isEmpty())
        {
            isIdentical = false;
        }
        
        // If dependent is generic, then it will be build on the first platform in this iteration
        // All is good
        
        if( isGeneric_d)
            return true;
        
        //  If I am generic and the dependent is generic, then I can be built at this time
        // 
        //  Will not reach here as we have already said that if dependenbt is generic, then all is good
        //  if (isGeneric_d && isGeneric)
        //  {
        //      return true;
        //  }


        //  If I am generic then I must wait for ALL dependent to be built on all platforms
        //  Thus I can't be built this round
        //
        if (isGeneric)
            return false;
        
        //
        //  If the two sets of build standards don't have generic, BUT are identical
        //  the we can assume that there is no cross breading. and that windows bits build from windows bits
        //  and solaris bits build from solaris
        //
        if (isIdentical)
            return true;
        
        //  The two sets of build standards are not an exact match
        //  Cheap solution: Assume that I can't be built this round
        //  Possible solution: If I am a subset of the dependent - then I can be built
        //                     If I am a superset of the dependent - then ???
        return false;
      
    }

    /**
     * Applies the required version number change. Will calculate mNextVersion
     * while not changing mVersion.
     * 
     * Special cases
     *  Cots packages - Can ripple if they have a patchBuild number suffix
     *  Test Builds - do as 99.99.99000
     *  Test Builds of Cots - Can do if they have a patchBuild suffix - do as .99000.cots
     * 
     * @param releaseManager    Release Manager instance to work against
     * 
     * @return 0 : success
     *         1 : cannot work with non standard versioning
     *         2 : ripple field limitations prevent a ripple build
     *         3 : Invalid Change Type
     * @exception Exception
     */
    
    int applyPV(ReleaseManager releaseManager) throws Exception
    {
        String logInfo = "applyPV," + mName;
        String changeType = "";
        mLogger.warn("applyPV on Package {}", mName);
        boolean isaCots = mExtension.compareTo(".cots") == 0 || mExtension.compareTo(".tools") == 0;
        
        //
        //  This method used to actually perform the version change
        //  Now it just calculates the potential value
        //  Must not alter mVersion as it will be used if the package is not selected to be built,
        //  but some of the code assumes that it can be.
        //
        String originalVersion = mVersion;
        
        //
        // Four scenarios, only applyPV for 3 of them
        // mDirectlyPlanned mIndirectlyPlanned mArchivalExistence mForcedRipple
        // Action
        // WIP/test build exists: true true don't care don't care applyPV
        // Package version is out of date: false true true don't care applyPV
        // Forced ripple: false true don't care > 0 applyPV
        // Package version does not exist: false true false = 0 do not applyPV
        //
        if (!mDirectlyPlanned && mIndirectlyPlanned && !mArchivalExistence && mForcedRippleInstruction == 0)
        {
            // the package has an mIndirectlyPlanned flag set true in daemon
            // mode because the package does not exist in an archive
            // do not apply a different package version
            mLogger.warn("applyPV. Rebuild Package {}", mName);
            mLogger.info("applyPv returned 0");
            return 0;
        }

        // override - no longer doing a rebuild - version number change from this point on
        if (mTestBuildInstruction == 0)
        {
            mRequiresSourceControlInteraction = true;
        }
        
        //      Force test builds of non-COTS packages to use a sensible version number
        if (mTestBuildInstruction > 0 && !isaCots )
        {
                mChangeType.resetData();                // Resolve conflict via build numbers
                mRippleField.setBuild();
                mVersion = "99.99.98999";               // Such that rippling build number will goto 99.99.99000
        }

        //
        // Detect invalid change type
        // Flagged when package instance is created
        //
        if (mChangeType.mUnknown)
        {
            mLogger.warn("Package Version unknown on Package {} Version: {}", mName, mVersion);
            mLogger.info("applyPv returned 3");
            return 3;
        }

        // If we are not calculating the new package version because the user
        // has fixed the version of the package. We are given the new package version.
        if (mChangeType.mFixed)
        {
            // mVersion is already setup

            mNextVersion = mFixedVersion;
            mLogger.warn("Package Version specified on Package {} New Version: {}", mName,  mNextVersion);
            mLogger.info("applyPv returned 0");
            return 0;
        }

        // We need to calculate the new version number
        //
        MutableInt major = new MutableInt(0);
        MutableInt minor = new MutableInt(0);
        MutableInt patch = new MutableInt(1000);

        // Planned packages have a previous version number to be used as the bases for the
        // calculation. Ripples won't.
        //
        if (mPrevVersion != null)
        {
                mVersion = mPrevVersion;
        }
        
        String[] field = mVersion.split("\\D");
        String nonStandardCotsVersion = "";
        logInfo += ", Prev:" + mVersion;

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

                // check patchStr is the last (at least 4) digits
                if (patchLen > 3
                        && mVersion.substring(mVersion.length() - patchLen, mVersion.length()).compareTo(patchStr) == 0)
                {
                    patch.value = Integer.parseInt(patchStr);
                    nonStandardCotsVersion = mVersion.substring(0, mVersion.length() - patchLen);
                }
            }

            if (nonStandardCotsVersion.length() == 0)
            {
                // cannot work with non standard version number
                mLogger.warn("applyPV cannot work with non standard versioning");
                mLogger.info("applyPv returned 1");
                return 1;
            }
        }

        if (nonStandardCotsVersion.length() == 0 && patch.value < 1000 && field[2].substring(0, 1).compareTo("0") != 0)
        {
            mLogger.info("applyPV accomodate old style Version of the form 1.0.1");
            patch.value = patch.value * 1000;
        }
        
        //      Force test builds of COTS packages to use a sensible version number
        if (mTestBuildInstruction > 0 )
        {
                mChangeType.resetData();                // Resolve conflict via build numbers
                mRippleField.setBuild();
                patch.value = 98999;                    // Such that rippling build number will goto xxxx.99000
        }

        // mChangeType overrides mRippleField
        do
        {
            if (mChangeType.mMajor)
            {
                changeType = ",CT Major";
                if (!incrementFieldsAccordingToLimits(4, major, minor, patch))
                {
                    mLogger.info("applyPv returned 2");
                    mVersion = originalVersion;
                    return 2;
                }
            } else if (mChangeType.mMinor)
            {
                changeType = ",CT Minor";
                if (!incrementFieldsAccordingToLimits(3, major, minor, patch))
                {
                    mLogger.info("applyPv returned 2");
                    mVersion = originalVersion;
                    return 2;
                }
            } else if (mChangeType.mPatch)
            {
                changeType = ",CT Patch";
                if (!incrementFieldsAccordingToLimits(2, major, minor, patch))
                {
                    mLogger.info("applyPv returned 2");
                    mVersion = originalVersion;
                    return 2;
                }
            } else
            {
                if (mRippleField.mMajor)
                {
                    changeType = ",R Major";
                    major.value++;
                    mLogger.info("applyPV mRippleField.mMajor {}", major.value);
                    minor.value = 0;
                    patch.value = 0;
                } else if (mRippleField.mMinor)
                {
                    changeType = ",R Minor";
                    minor.value++;
                    mLogger.info("applyPV mRippleField.mMinor {}", minor.value);
                    patch.value = 0;
                } else if (mRippleField.mPatch)
                {
                    changeType = ",R Patch";
                    patch.value = ((patch.value / 1000) + 1) * 1000; 
                    mLogger.info("applyPV mRippleField.mPatch {}", patch.value);
                } else if (mRippleField.mBuild)
                {
                    changeType = ", R Build";
                    patch.value++;
                    mLogger.info("applyPV mRippleField.mBuild {}", patch.value);
                } else
                {
                    if (!incrementFieldsAccordingToLimits(1, major, minor, patch))
                    {
                        mLogger.info("applyPv returned 2");
                        mVersion = originalVersion;
                        return 2;
                    }
                }
            }

            if (nonStandardCotsVersion.length() == 0)
            {
                mVersion = String.valueOf(major.value) + "." + String.valueOf(minor.value) + ".";
            } else
            {
                mVersion = nonStandardCotsVersion;
            }

            if (patch.value < 10)
            {
                mVersion += "000";
            } else if (patch.value < 100)
            {
                mVersion += "00";
            } else if (patch.value < 1000)
            {
                mVersion += "0";
            }

            mVersion += String.valueOf(patch.value);
        } while (exists(releaseManager));

        logInfo += changeType + ", Next Version:" + mVersion;
        mLogger.warn(logInfo);
        mLogger.info("applyPv returned 0");
        mNextVersion = mVersion;
        mVersion = originalVersion;
        return 0;
    }

    /**
     * increments fields according to mRippleField.mLimit if necessary will
     * apply it to the field passed as follows 1 = build 2 = patch 3 = minor
     * other = major returns true on success false on ripple field limitations
     * prevent a ripple build
     */
    private boolean incrementFieldsAccordingToLimits(int field, MutableInt major, MutableInt minor, MutableInt patch)
    {
        boolean retVal = true;

        if (!mChangeType.mLimit && !mRippleField.mLimit)
        {
            // simple case
            // no need to take field limits into consideration
            switch (field)
            {
            case 1:
                // unreachable
                // the only scenario involving build number manipulation
                // involves the mRippleField.mLimit being set
                retVal = false;
                break;
            case 2:
                do
                {
                    patch.value++;
                } while ((patch.value / 1000) * 1000 != patch.value);
                mLogger.info("incrementFieldsAccordingToLimits patch " + patch.value);
                break;
            case 3:
                minor.value++;
                mLogger.info("incrementFieldsAccordingToLimits minor " + minor.value);
                patch.value = 0;
                break;
            default:
                major.value++;
                mLogger.info("incrementFieldsAccordingToLimits major " + major.value);
                minor.value = 0;
                patch.value = 0;
            }
        } else
        {
            // take field limits into consideration
            boolean changeOccurred = false;
            boolean incrementField = true;

            switch (field)
            {
            case 1:
                if (mBuildLimit != 0)
                {
                    // increment or reset the patch build number
                    int buildNumber = patch.value - (patch.value / 1000) * 1000;

                    if (buildNumber < mBuildLimit)
                    {
                        // can increment the patch build number
                        patch.value++;
                        mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit build number " + patch.value);
                        changeOccurred = true;
                        incrementField = false;
                    } else
                    {
                        if (mPatchLimit == 0)
                        {
                            // reset the patch number and patch build number
                            patch.value = 0;
                        }
                    }
                }
                // no break by design
            case 2:
                if (mPatchLimit != 0 && incrementField)
                {
                    // increment or reset the patch number
                    if ((patch.value / 1000) < mPatchLimit)
                    {
                        do
                        {
                            patch.value++;
                        } while ((patch.value / 1000) * 1000 != patch.value);

                        mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit patch " + patch.value);
                        changeOccurred = true;
                        incrementField = false;
                    } else
                    {
                        // reset the patch number and patch build number
                        patch.value = 0;
                    }
                }
                // no break by design
            case 3:
                if (mMinorLimit != 0 && incrementField)
                {
                    // increment or reset the minor number
                    if (minor.value < mMinorLimit)
                    {
                        minor.value++;
                        patch.value = 0;
                        mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit minor " + minor.value);
                        changeOccurred = true;
                        incrementField = false;
                    } else
                    {
                        // reset the minor number
                        minor.value = 0;
                    }
                }
                // no break by design
            default:
                if (mMajorLimit != 0 && incrementField)
                {
                    // increment or reset the major number
                    if (major.value < mMajorLimit)
                    {
                        // increment the major number
                        changeOccurred = true;
                        major.value++;
                        minor.value = 0;
                        patch.value = 0;
                        mLogger.info("incrementFieldsAccordingToLimits mRippleField.mLimit major " + major.value);
                    }
                }
            }

            if (!changeOccurred)
            {
                // unable to increment a field due to field limitations
                mLogger.warn("incrementFieldsAccordingToLimits ripple field limitations prevent a ripple build");
                mLogger.info("incrementFieldsAccordingToLimits returned false");
                retVal = false;
            }
        }

        return retVal;
    }

    /**
     * Check if a specified Version of the package exists in dpkg_archive or the Release Manager Database
     * 
     * @param releaseManager Release Manager Instance
     * 
     * @return True if the Package Version exists within the Release Manager Database
     * @exception Exception
     */
    private boolean exists(ReleaseManager releaseManager) throws Exception
    {
        mLogger.debug("exists on Package " + mName + " version " + mVersion + " extension " + mExtension);
        boolean retVal = false;

        if (!releaseManager.mUseDatabase)
        {
            mLogger.info("exists !releaseManager.mUseDatabase");
        }
        else
        {
            //  Check Package Archive
            retVal = existsInDpkgArchive();
            if (!retVal)
            {
                //  Check Release Manager Database
                retVal = releaseManager.queryPackageVersions(mPid, mVersion + mExtension);
            }
        }

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


    /**
     * Check to see if a package exists in dpkg_archive
     * 
     * @return true if the version exists in dpkg_archive
     * @exception Exception Thrown if dpkg_archive does not exist. The 'cause' of 'mRecoverable' is special and
     *                      will be trapped later to determine if this is a recoverable exception.
     */
    boolean existsInDpkgArchive() throws Exception
    {
        mLogger.debug("existsInDpkgArchive on " + mName);
        boolean retVal = false;
        String name = utilities.catDir(mName, mVersion + mExtension );

        //  If a replica exists, then check it first
        //  If we are configured with a replica its because access to the main archive is slow
        //  and we want this check to be fast
        //
        if (mGbeDpkgReplica != null && mGbeDpkgReplica.length() > 0)
        {
            File dpkg = new File(mGbeDpkgReplica);
            if (!dpkg.exists())
            {
                mLogger.error("existsInDpkgArchive. mGbeDpkgReplica not accessable. {}", mRecoverable);
                throw new Exception(mRecoverable);
            }
            
            if( utilities.freshFileExists(utilities.catDir(mGbeDpkgReplica, name) ) )
            {
                mLogger.info("existsInDpkgArchive mGbeDpkgReplica");
                retVal = true;
            }
        }
       
        //  Check (possibly remote) dpkg_archive for files existence if it was not found locally
        //
        if ( !retVal )
        {
    
            //  If the package archive does not exist at the moment, then we have a network issue
            //  This is a recoverable error
            
            File dpkg = new File(mGbeDpkg);
            if (!dpkg.exists())
            {
                mLogger.error("existsInDpkgArchive. mGbeDpkg not accessable. {}", mRecoverable);
                throw new Exception(mRecoverable);
            }
    
            if( utilities.freshFileExists(utilities.catDir(mGbeDpkg, name) ) )
            {
                mLogger.info("existsInDpkgArchive mGbeDpkg");
                retVal = true;
            }
        }
        
        mArchivalExistence = retVal;
        mLogger.debug("existsInDpkgArchive returned {}", retVal);
        return retVal;
    }
    
    /**
     * returns true if the required package archives (dpkg_archive) exist
     * attempt to recover from their transient loss
     */
    public static boolean recover()
    {
        mLogger.debug("recover");
        boolean retVal = false;

        String Release = mGbeDpkg;
        if (Release != null)
        {
            if ( utilities.freshFileExists(mGbeDpkg) )
            {
                retVal = true;
                mLogger.error("recover: dpkg_archive access has been restored");
            }
        }

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

    /**
     * Returns a data structure of unique email addresses
     * Uses the Global Email Collection and the packages own failure email collection
     */
    private LinkedHashSet<String>buildEmailList(RippleEngine rippleEngine)
    {
        //  Create a single list of email targets ensuring only one instance of each email address
        //  ie: Remove duplicates, null and empty strings
        LinkedHashSet<String> hs = new LinkedHashSet<String>();
        
        // Global and Project Wide emails
        for (Iterator<String> it = rippleEngine.mMailGlobalCollection.iterator(); it.hasNext();)
        {
            String item = it.next();
            if (item != null && item.length() > 0) 
            {
                hs.add(item);
            }
        }
        
        // Package specific collection
        for (Iterator<String> it = mBuildFailureEmailCollection.iterator(); it.hasNext();)
        {
            String item = it.next();
            if (item != null && item.length() > 0) 
            {
                hs.add(item);
            }
        }

        return hs;
    }
    
    /**
     * Add email information in a form suitable for creating an Ant file
     * @param   rippleEngine    - Ripple Engine Instance
     * @param   xml             - An XmlBuilder element to extend
     */
    void emailInfo(RippleEngine rippleEngine, XmlBuilder xml)
    {
        
        //  Create a single list of email targets ensuring only one instance of each email address
        //  ie: Remove duplicates
        LinkedHashSet<String> hs = buildEmailList(rippleEngine);
        
        for (Iterator<String> it = hs.iterator(); it.hasNext();)
        {
            String email = it.next();
            XmlBuilder entry = xml.addNewElement("owner");
            entry.addAttribute("email", email);
        }
    }

    /**
     * Returns email information in a form suitable for direct use
     * @param   rippleEngine Current Release Manager context
     * @return  A comma separated list of user names. May return a 'null' String
     */
    String emailInfoNonAntTask(RippleEngine rippleEngine)
    {
        //  Create a single list of email targets ensuring only one instance of each email address
        //  ie: Remove duplicates
        LinkedHashSet<String> hs = buildEmailList(rippleEngine);

        String retVal = null;
        for (Iterator<String> it = hs.iterator(); it.hasNext();)
        {
            String email = it.next();

            if (retVal == null)
            {
                retVal = "";
            } else
            {
                retVal += ",";
            }
            retVal += email;
        }

        return retVal;
    }

    /**
     * Adds email to mBuildFailureEmailCollection.
     * Do not worry about multiple entries. These will be handled when the data is extracted
     * @param   email   - Email address. Null is allowed and will not be added
     */
    public void addEmail(String email)
    {
        if (email != null) 
        {
            mBuildFailureEmailCollection.add(email);
        }
    }

    /**
     * Returns true if the package is a part of a circular dependency within the provided collection
     * 
     * If the package depends on a package with a circular dependency then the function
     * will return false.
     */
    public boolean hasCircularDependency(PackageCollection packageCollection)
    {
        mLogger.debug("hasCircularDependency: {}", mAlias);
        boolean retVal = detectCircularDependency(mAlias, packageCollection, null);
        mLogger.debug("hasCircularDependency returned {} ", retVal);
        return retVal;
    }
    
    /** Reset the circular dependency testing information
     * 
     * @param packageCollection - Collection of packages to process
     */
    public static void resetCircularDependency(PackageCollection packageCollection)
    {
        for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
        {
            Package p = it.next();
            p.mCheckedCircularDependency = false;
            p.mHasCircularDependency = false;
            p.mBreadCrumb = 0;
        }
    }

    /**
     * Returns true is a part of a circular dependency
     * Will examine all the packages sub dependencies and mark those that do have a
     * circular dependency.
     * 
     * This process works by descending the dependency tree and dropping a bread crumb
     * If the bread crumb is seen during the decent, then a circle has been detected and
     * the package (with the bread crumb) will be marked as having a circular dependency
     * 
     *  Assumes that the caller will walk ALL packages and flag those with a circular
     *  dependence AND those that depend on that package.
     * 
     */
    private boolean detectCircularDependency(String alias, PackageCollection packageCollection, Package parent)
    {
        mLogger.debug("detectCircularDependency");
        boolean retVal = false;
        
        // if this package has yet to be checked for circular dependency
        if (!mCheckedCircularDependency)
        {
            // Will be set as we drill down through dependencies
            // If we see this marker (bread crumb) then we have a loop
            if (mBreadCrumb != 0)
            {
                mBreadCrumb = 3;
                
                mHasCircularDependency = true;
                if(parent != null && parent.mBreadCrumb != 3 )
                {
                    parent.mBreadCrumb = 2;
                }
            }
            else
            {
                // Mark this package as potentially having a circular dependency
                // Will now drill down and see if we hit a marker
                mBreadCrumb = 1;
                
                // Recurse down the dependencies and sub dependencies
                for (Iterator<PkgDependency> it2 = mDependencyCollection.iterator(); it2.hasNext();)
                {
                    PkgDependency depEntry = it2.next();
                    Package depPackage = packageCollection.contains(depEntry.alias);
                    if ( depPackage != null ) {
                        depPackage.detectCircularDependency(alias, packageCollection, this);
                    }
                }
                
                if (mBreadCrumb == 2)
                {
                    mHasCircularDependency = true;
                    if(parent != null && parent.mBreadCrumb != 3 )
                    {
                        parent.mBreadCrumb = 2;
                    }
                }
                mBreadCrumb = 0;
            }

            // Flag package as having been examined
            mCheckedCircularDependency = true;
        } 

        // return the persisted circular dependency outcome
        retVal = mHasCircularDependency;
        mLogger.info("detectCircularDependency 2 returned {}", retVal);
        return retVal;
    }
    
    /** Set the sequence of all packages in the list
     *  Used so that the Unit Tests function can preserve the basic ordering of the list 
     */
    public static void setSequence(ArrayList<Package> al)
    {
        int seq = 1;
        for (Iterator<Package> it = al.iterator(); it.hasNext(); )
        {
            Package p = it.next();
            p.mSeqId = seq++;
        }
    }

    /** Reset the processed flag on a collection of packages
     * 
     */
    public static void resetProcessed (PackageCollection packageCollection)
    {
        for (Iterator<Package> it = packageCollection.iterator(); it.hasNext(); )
        {
            Package p = it.next();
            p.mIsProcessed = false;
            p.mProcessed = false;
            if (p.mBuildFile > 0)
                p.mBuildFile = 0;
        }
    }
    
    /** Comparator for sorting package collections by mSeqId
     *  Used to preserve order for unit testing
     */
    public static final Comparator<Package> SeqComparator = new Comparator<Package>() {
        
        /**
         * Returns -ve: p1 is less than p2
         *           0: p1 = p2
         *         +ve: p1 > p2
         */
        public int compare (Package p1, Package p2) {
                return p1.mSeqId - p2.mSeqId;
        }
    };
    
    /**
     * entity class supporting the ERG version numbering standard:
     * <major>.<minor>.<patch/build> patch/build is at least a 4 digit number
     * whose last 3 digits represent the build
     */
    public class VersionNumberingStandard
    {
        /**
         * in terms of the mChangeType Package field, when true indicates the
         * contract of the package has changed in a non backwardly compatible
         * manner in terms of the mRippleField Package field, when true indicates
         * the major version number will be incremented
         * 
         * @attribute
         */
        private boolean mMajor = false;

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

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

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

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

        /**
         * in terms of the mChangeType Package field, when true indicates the
         * package version number will not be rippled. The user will have fixed
         * the version number. This is only application to WIP packages
         * 
         * @attribute
         */
        private boolean mFixed = false;

        /**
         * in terms of the mChangeType Package field, when true indicates the
         * method of rippling a package version number is not known.
         * 
         * @attribute
         */
        private boolean mUnknown = false;

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

        /**
         * Reset all values to a known state
         * 
         */
        void resetData()
        {
            mBuild = false;
            mMajor = false;
            mMinor = false;
            mPatch = false;
            mLimit = false;
            mFixed = false;
            mUnknown = false;
        }

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

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

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

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

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

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

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

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

        /**
         * sets parameters to indicate that the change type is Fixed. The
         * version number is set by the user and a ripple will not be calculated
         */
        void setFixed()
        {
            mLogger.debug("setFixed");
            resetData();
            mFixed = true;
        }

        /**
         * Sets parameters to indicate that the change type is not known
         * 
         */
        void setUnknown()
        {
            resetData();
            mUnknown = true;
        }

    }

    /**
     *  Add a package dependency
     *  @param p - The package to add as a dependent. Uses data from the provided package
     *  @return - The current package to allow chaining of calls
     */
    public Package addDependency(Package p) {
        mDependencyCollection.add(new PkgDependency(p.mAlias, p.mId ));
        return this;
    }
    
    /**
     *  Add a package dependency
     *  @param alias - Alias of the dependent package
     *  @param pvId  - pvId of the dependent package
     *  @return - The current package to allow chaining of calls
     */
    public Package addDependency(String alias, Integer pvId) {
        mDependencyCollection.add(new PkgDependency(alias, pvId ));
        return this;
    }

    /** Clear the packages list of dependencies
     *  UTF use
     *  @return - The current package to allow chaining of calls
     */
    public Package resetDependencies() {
        mDependencyCollection.clear();
        return this;
    }

}