Version.java

/*
 * Copyright (c) 2002-2021, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.releaser.util.version;

import fr.paris.lutece.plugins.releaser.util.ReleaserUtils;
import fr.paris.lutece.portal.service.util.AppLogService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;

// TODO: Auto-generated Javadoc
/**
 * Version.
 */
public class Version implements Comparable
{

    /** The Constant NOT_AVAILABLE. */
    public static final String NOT_AVAILABLE = "Not available";

    /** The Constant QUALIFIER_SNAPSHOT. */
    private static final String QUALIFIER_SNAPSHOT = "SNAPSHOT";

    /** The Constant QUALIFIER_CANDIDATE. */
    private static final String QUALIFIER_CANDIDATE = "RC";

    /** The Constant PATTERN_NUMBER. */
    private static final String PATTERN_NUMBER = "\\d+";

    /** The Constant QUALIFIER_VERSION_FORMAT. */
    private static final String QUALIFIER_VERSION_FORMAT = "%02d";

    /** The n major. */
    private int _nMajor;

    /** The n minor. */
    private int _nMinor;

    /** The n patch. */
    private int _nPatch;

    /** The str qualifier. */
    private String _strQualifier;

    /** The str qualifier radix. */
    private String _strQualifierRadix;

    /** The n qualifier number. */
    private int _nQualifierNumber;

    /**
     * Constructor.
     */
    public Version( )
    {
    }

    /**
     * Constructor.
     *
     * @param nMajor
     *            major digit
     * @param nMinor
     *            minor digit
     * @param nPatch
     *            patch digit
     * @param strQualifier
     *            qualifier
     */
    public Version( int nMajor, int nMinor, int nPatch, String strQualifier )
    {
        _nMajor = nMajor;
        _nMinor = nMinor;
        _nPatch = nPatch;
        _strQualifier = strQualifier;
    }

    /**
     * Gets the major.
     *
     * @return the nMajor
     */
    public int getMajor( )
    {
        return _nMajor;
    }

    /**
     * Sets the major.
     *
     * @param nMajor
     *            the nMajor to set
     */
    public void setMajor( int nMajor )
    {
        _nMajor = nMajor;
    }

    /**
     * Gets the minor.
     *
     * @return the nMinor
     */
    public int getMinor( )
    {
        return _nMinor;
    }

    /**
     * Sets the minor.
     *
     * @param nMinor
     *            the nMinor to set
     */
    public void setMinor( int nMinor )
    {
        _nMinor = nMinor;
    }

    /**
     * Gets the patch.
     *
     * @return the nPatch
     */
    public int getPatch( )
    {
        return _nPatch;
    }

    /**
     * Sets the patch.
     *
     * @param nPatch
     *            the nPatch to set
     */
    public void setPatch( int nPatch )
    {
        _nPatch = nPatch;
    }

    /**
     * Gets the qualifier.
     *
     * @return the Qualifier
     */
    public String getQualifier( )
    {
        return _strQualifier;
    }

    /**
     * Sets the qualifier.
     *
     * @param strQualifier
     *            the Qualifier to set
     */
    public void setQualifier( String strQualifier )
    {
        Pattern pattern = Pattern.compile( PATTERN_NUMBER );
        Matcher matcher = pattern.matcher( strQualifier );
        if ( matcher.find( ) )
        {
            String strNumber = matcher.group( );
            _nQualifierNumber = Integer.parseInt( strNumber );
            _strQualifierRadix = strQualifier.substring( 0, strQualifier.indexOf( strNumber ) );
        }
        _strQualifier = strQualifier;
    }

    /**
     * Compare to.
     *
     * @param object
     *            the object
     * @return the int
     */
    @Override
    public int compareTo( Object object )
    {
        Version version = (Version) object;
        int nDiff = _nMajor - version.getMajor( );
        if ( nDiff != 0 )
        {
            return nDiff;
        }
        nDiff = _nMinor - version.getMinor( );
        if ( nDiff != 0 )
        {
            return nDiff;
        }
        nDiff = _nPatch - version.getPatch( );
        return nDiff;
    }

    /**
     * Gets the version.
     *
     * @return the version
     */
    public String getVersion( )
    {
        StringBuilder sbVersion = new StringBuilder( );
        sbVersion.append( _nMajor ).append( '.' ).append( _nMinor ).append( '.' ).append( _nPatch );
        if ( _strQualifier != null )
        {
            if ( _strQualifierRadix != null )
            {
                sbVersion.append( '-' ).append( _strQualifierRadix ).append( String.format( QUALIFIER_VERSION_FORMAT, _nQualifierNumber ) );
            }
            else
            {
                sbVersion.append( '-' ).append( _strQualifier );
            }
        }
        return sbVersion.toString( );
    }

    /**
     * {@inheritDoc }.
     *
     * @return the string
     */
    @Override
    public String toString( )
    {
        return getVersion( );
    }

    /**
     * Parse a string to extract version.
     *
     * @param strSource
     *            The source
     * @return The version object
     * @throws VersionParsingException
     *             if parsing failed
     */
    public static Version parse( String strSource ) throws VersionParsingException
    {
        Version version = new Version( );

        try
        {

            String strCurrent = strSource != null ? strSource.trim( ) : "";

            // Search for qualifier
            int nPos = strCurrent.indexOf( '-' );
            if ( nPos != -1 )
            {
                version.setQualifier( strCurrent.substring( nPos + 1 ) );
                strCurrent = strCurrent.substring( 0, nPos );
            }

            // Search for major digits
            nPos = strCurrent.indexOf( '.' );

            String strMajor = strCurrent.substring( 0, nPos );
            version.setMajor( ReleaserUtils.convertStringToInt( strMajor ) );

            // Search for minor digits
            strCurrent = strCurrent.substring( nPos + 1 );
            nPos = strCurrent.indexOf( '.' );

            if ( nPos != -1 )
            {
                String strMinor = strCurrent.substring( 0, nPos );
                version.setMinor( ReleaserUtils.convertStringToInt( strMinor ) );

                strCurrent = strCurrent.substring( nPos + 1 );
                version.setPatch( ReleaserUtils.convertStringToInt( strCurrent ) );
            }
            else
            {
                version.setMinor( ReleaserUtils.convertStringToInt( strCurrent ) );
            }
        }
        catch( Exception e )
        {
            throw new VersionParsingException( "Error parsing version : '" + strSource + "' : " + e.getMessage( ), e );
        }
        return version;
    }

    /**
     * returns the snapshot version.
     *
     * @return the snapshot version
     */
    public Version snapshot( )
    {
        return new Version( _nMajor, _nMinor, _nPatch, QUALIFIER_SNAPSHOT );
    }

    /**
     * Build a new version object with major digit incremented.
     *
     * @param bSnapshot
     *            if snapshot qualifier needed
     * @return The next version object
     */
    public Version nextMajor( boolean bSnapshot )
    {
        String strQualifier = ( bSnapshot ) ? QUALIFIER_SNAPSHOT : null;

        return new Version( _nMajor + 1, 0, 0, strQualifier );
    }

    /**
     * Build a new version object with minor digit incremented.
     *
     * @param bSnapshot
     *            if snapshot qualifier needed
     * @return The next version object
     */
    public Version nextMinor( boolean bSnapshot )
    {
        String strQualifier = ( bSnapshot ) ? QUALIFIER_SNAPSHOT : null;

        return new Version( _nMajor, _nMinor + 1, 0, strQualifier );
    }

    /**
     * Build a new version object with patch digit incremented.
     *
     * @param bSnapshot
     *            if snapshot qualifier needed
     * @return The next version object
     */
    public Version nextPatch( boolean bSnapshot )
    {
        String strQualifier = ( bSnapshot ) ? QUALIFIER_SNAPSHOT : null;

        return new Version( _nMajor, _nMinor, _nPatch + 1, strQualifier );
    }

    /**
     * Build a new version object with no qualifier.
     *
     * @return The next version object
     */
    public Version nextRelease( )
    {
        int nPatch = ( isSnapshot( ) || isCandidate( ) ) ? _nPatch : _nPatch + 1;
        return new Version( _nMajor, _nMinor, nPatch, null );
    }

    /**
     * Next candidate.
     *
     * @return the version
     */
    private Version nextCandidate( )
    {
        String strQualifier;
        int nPatch = _nPatch;
        if ( ( _strQualifierRadix != null ) && ( _strQualifierRadix.equals( "RC-" ) ) )
        {
            strQualifier = String.format( "RC-%02d", _nQualifierNumber + 1 );
        }
        else
        {
            if ( !QUALIFIER_SNAPSHOT.equals( _strQualifier ) )
            {
                nPatch += 1;
            }
            strQualifier = "RC-01";
        }
        return new Version( _nMajor, _nMinor, nPatch, strQualifier );
    }

    /**
     * Returns true if the version is qualified as Snapshot.
     *
     * @return true if the version is qualified as Snapshot
     */
    public boolean isSnapshot( )
    {
        return QUALIFIER_SNAPSHOT.equals( _strQualifier );
    }

    /**
     * Returns true if the version is qualified as Candidate.
     *
     * @return true if the version is qualified as Candidate
     */
    public boolean isCandidate( )
    {
        return ( _strQualifier != null ) && ( _strQualifier.startsWith( QUALIFIER_CANDIDATE ) );
    }

    /**
     * Check if a given version is a SNAPSHOT.
     *
     * @param strVersion
     *            The version to check
     * @return True if snapshot otherwise false
     */
    public static boolean isSnapshot( String strVersion )
    {
        try
        {
            Version version = parse( strVersion );
            return version.isSnapshot( );
        }
        catch( VersionParsingException ex )
        {
            AppLogService.error( "Error parsing version " + strVersion + " : " + ex.getMessage( ), ex );
        }
        return false;
    }

    /**
     * Check if a given version is a RELEASE CANDIDATE.
     *
     * @param strVersion
     *            The version to check
     * @return True if candidate otherwise false
     */
    public static boolean isCandidate( String strVersion )
    {
        try
        {
            Version version = parse( strVersion );
            return version.isCandidate( );
        }
        catch( VersionParsingException ex )
        {
            AppLogService.error( "Error parsing version " + strVersion + " : " + ex.getMessage( ), ex );
        }
        return false;
    }

    /**
     * Get a list of next versions for a given version.
     *
     * @param strPreviousReleaseVersion
     *            The current version
     * @return The list
     */
    public static List<String> getNextReleaseVersions( String strPreviousReleaseVersion )
    {
        List<String> listVersions = new ArrayList<>( );
        try
        {
            Version version = parse( strPreviousReleaseVersion );
            listVersions.add( version.nextCandidate( ).getVersion( ) );
            listVersions.add( version.nextRelease( ).getVersion( ) );
            listVersions.add( version.nextMinor( false ).getVersion( ) );
            listVersions.add( version.nextMajor( false ).getVersion( ) );
        }
        catch( VersionParsingException ex )
        {
            AppLogService.error( "Error parsing version " + strPreviousReleaseVersion + " : " + ex.getMessage( ), ex );
        }
        return listVersions;
    }

    /**
     * Get the next snapshot version for a given version.
     *
     * @param strVersion
     *            The current version
     * @return The version
     */
    public static String getNextSnapshotVersion( String strVersion )
    {
        String strSnapshotVersion = NOT_AVAILABLE;
        try
        {
            Version version = Version.parse( strVersion );
            boolean bSnapshot = true;
            if ( version.isCandidate( ) )
            {
                strSnapshotVersion = version.snapshot( ).getVersion( );
            }
            else
            {
                strSnapshotVersion = version.nextPatch( bSnapshot ).getVersion( );
            }
        }
        catch( VersionParsingException ex )
        {
            AppLogService.error( "Error parsing version " + strVersion + " : " + ex.getMessage( ), ex );
        }
        return strSnapshotVersion;
    }

    /**
     * Get the next release version for a given version.
     *
     * @param strVersion
     *            The current version
     * @return The version
     */
    public static String getReleaseVersion( String strVersion )
    {
        String strTargetVersion = Version.NOT_AVAILABLE;
        try
        {
            strTargetVersion = Version.parse( strVersion ).nextRelease( ).getVersion( );
        }
        catch( VersionParsingException ex )
        {
            AppLogService.error( "Error parsing version " + strVersion + " : " + ex.getMessage( ), ex );
        }
        return strTargetVersion;
    }

}