Coverage Report - org.apache.maven.shared.release.versions.DefaultVersionInfo
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultVersionInfo
98%
102/104
91%
51/56
2,722
 
 1  
 package org.apache.maven.shared.release.versions;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import org.apache.maven.artifact.Artifact;
 23  
 import org.apache.maven.artifact.ArtifactUtils;
 24  
 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 25  
 import org.codehaus.plexus.util.StringUtils;
 26  
 
 27  
 import java.util.ArrayList;
 28  
 import java.util.Arrays;
 29  
 import java.util.List;
 30  
 import java.util.Locale;
 31  
 import java.util.regex.Matcher;
 32  
 import java.util.regex.Pattern;
 33  
 
 34  
 /**
 35  
  * This compares and increments versions for a common java versioning scheme.
 36  
  * <p/>
 37  
  * The supported version scheme has the following parts.<br>
 38  
  * <code><i>component-digits-annotation-annotationRevision-buildSpecifier</i></code><br>
 39  
  * Example:<br>
 40  
  * <code>my-component-1.0.1-alpha-2-SNAPSHOT</code>
 41  
  * <p/>
 42  
  * <ul>Terms:
 43  
  * <li><i>component</i> - name of the versioned component (log4j, commons-lang, etc)
 44  
  * <li><i>digits</i> - Numeric digits with at least one "." period. (1.0, 1.1, 1.01, 1.2.3, etc)
 45  
  * <li><i>annotationRevision</i> - Integer qualifier for the annotation. (4 as in RC-4)
 46  
  * <li><i>buildSpecifier</i> - Additional specifier for build. (SNAPSHOT, or build number like "20041114.081234-2")
 47  
  * </ul>
 48  
  * <b>Digits is the only required piece of the version string, and must contain at lease one "." period.</b>
 49  
  * <p/>
 50  
  * Implementation details:<br>
 51  
  * The separators "_" and "-" between components are also optional (though they are usually recommended).<br>
 52  
  * Example:<br>
 53  
  * <code>log4j-1.2.9-beta-9-SNAPSHOT == log4j1.2.9beta9SNAPSHOT == log4j_1.2.9_beta_9_SNAPSHOT</code>
 54  
  * <p/>
 55  
  * Leading zeros are significant when performing comparisons.
 56  
  * <p/>
 57  
  * TODO: this parser is better than DefaultArtifactVersion - replace it with this (but align naming) and then remove this from here.
 58  
  */
 59  124
 public class DefaultVersionInfo
 60  
     implements VersionInfo
 61  
 {
 62  
     private final String strVersion;
 63  
 
 64  
     private final List<String> digits;
 65  
 
 66  
     private String annotation;
 67  
 
 68  
     private String annotationRevision;
 69  
 
 70  
     private final String buildSpecifier;
 71  
 
 72  
     private String annotationSeparator;
 73  
 
 74  
     private String annotationRevSeparator;
 75  
 
 76  
     private final String buildSeparator;
 77  
 
 78  
     private static final int DIGITS_INDEX = 1;
 79  
 
 80  
     private static final int ANNOTATION_SEPARATOR_INDEX = 2;
 81  
 
 82  
     private static final int ANNOTATION_INDEX = 3;
 83  
 
 84  
     private static final int ANNOTATION_REV_SEPARATOR_INDEX = 4;
 85  
 
 86  
     private static final int ANNOTATION_REVISION_INDEX = 5;
 87  
 
 88  
     private static final int BUILD_SEPARATOR_INDEX = 6;
 89  
 
 90  
     private static final int BUILD_SPECIFIER_INDEX = 7;
 91  
 
 92  
     private static final String SNAPSHOT_IDENTIFIER = "SNAPSHOT";
 93  
 
 94  
     private static final String DIGIT_SEPARATOR_STRING = ".";
 95  
 
 96  2
     public static final Pattern STANDARD_PATTERN = Pattern.compile(
 97  
         "^((?:\\d+\\.)*\\d+)"      // digit(s) and '.' repeated - followed by digit (version digits 1.22.0, etc)
 98  
         + "([-_])?"                // optional - or _  (annotation separator)
 99  
         + "([a-zA-Z]*)"            // alpha characters (looking for annotation - alpha, beta, RC, etc.)
 100  
         + "([-_])?"                // optional - or _  (annotation revision separator)
 101  
         + "(\\d*)"                 // digits  (any digits after rc or beta is an annotation revision)
 102  
         + "(?:([-_])?(.*?))?$" );  // - or _ followed everything else (build specifier)
 103  
 
 104  
     /* *
 105  
      * cmaki 02242009
 106  
      * FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT
 107  
      * This alternate pattern supports version numbers like:
 108  
      * trunk-SNAPSHOT
 109  
      * branchName-SNAPSHOT
 110  
      * SNAPSHOT
 111  
      */
 112  2
     public static final Pattern ALTERNATE_PATTERN = Pattern.compile(
 113  
         "^(SNAPSHOT|[a-zA-Z]+[_-]SNAPSHOT)"      // for SNAPSHOT releases only (possible versions include: trunk-SNAPSHOT or SNAPSHOT)
 114  
     );
 115  
 
 116  
     /**
 117  
      * Constructs this object and parses the supplied version string.
 118  
      *
 119  
      * @param version
 120  
      */
 121  
     public DefaultVersionInfo( String version )
 122  
         throws VersionParseException
 123  444
     {
 124  444
         strVersion = version;
 125  
 
 126  
         // FIX for non-digit release numbers, e.g. trunk-SNAPSHOT or just SNAPSHOT
 127  444
         Matcher matcher = ALTERNATE_PATTERN.matcher( strVersion );
 128  
         // TODO: hack because it didn't support "SNAPSHOT"
 129  444
         if ( matcher.matches() )
 130  
         {
 131  14
             annotation = null;
 132  14
             digits = null;
 133  14
             buildSpecifier = version;
 134  14
             buildSeparator = null;
 135  14
             return;
 136  
         }
 137  
 
 138  430
         Matcher m = STANDARD_PATTERN.matcher( strVersion );
 139  430
         if ( m.matches() )
 140  
         {
 141  420
             digits = parseDigits( m.group( DIGITS_INDEX ) );
 142  420
             if ( !SNAPSHOT_IDENTIFIER.equals( m.group( ANNOTATION_INDEX ) ) )
 143  
             {
 144  370
                 annotationSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
 145  370
                 annotation = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
 146  
 
 147  370
                 if ( StringUtils.isNotEmpty( m.group( ANNOTATION_REV_SEPARATOR_INDEX ) )
 148  
                     && StringUtils.isEmpty( m.group( ANNOTATION_REVISION_INDEX ) ) )
 149  
                 {
 150  
                     // The build separator was picked up as the annotation revision separator
 151  4
                     buildSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
 152  4
                     buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
 153  
                 }
 154  
                 else
 155  
                 {
 156  366
                     annotationRevSeparator = m.group( ANNOTATION_REV_SEPARATOR_INDEX );
 157  366
                     annotationRevision = nullIfEmpty( m.group( ANNOTATION_REVISION_INDEX ) );
 158  
 
 159  366
                     buildSeparator = m.group( BUILD_SEPARATOR_INDEX );
 160  366
                     buildSpecifier = nullIfEmpty( m.group( BUILD_SPECIFIER_INDEX ) );
 161  
                 }
 162  
             }
 163  
             else
 164  
             {
 165  
                 // Annotation was "SNAPSHOT" so populate the build specifier with that data
 166  50
                 buildSeparator = m.group( ANNOTATION_SEPARATOR_INDEX );
 167  50
                 buildSpecifier = nullIfEmpty( m.group( ANNOTATION_INDEX ) );
 168  
             }
 169  
         }
 170  
         else
 171  
         {
 172  10
             throw new VersionParseException( "Unable to parse the version string: \"" + version + "\"" );
 173  
         }
 174  420
     }
 175  
 
 176  
     public DefaultVersionInfo( List<String> digits, String annotation, String annotationRevision, String buildSpecifier,
 177  
                                String annotationSeparator, String annotationRevSeparator, String buildSeparator )
 178  68
     {
 179  68
         this.digits = digits;
 180  68
         this.annotation = annotation;
 181  68
         this.annotationRevision = annotationRevision;
 182  68
         this.buildSpecifier = buildSpecifier;
 183  68
         this.annotationSeparator = annotationSeparator;
 184  68
         this.annotationRevSeparator = annotationRevSeparator;
 185  68
         this.buildSeparator = buildSeparator;
 186  68
         this.strVersion = getVersionString( this, buildSpecifier, buildSeparator );
 187  68
     }
 188  
 
 189  
     public boolean isSnapshot()
 190  
     {
 191  14
         return ArtifactUtils.isSnapshot( strVersion );
 192  
     }
 193  
 
 194  
     public VersionInfo getNextVersion()
 195  
     {
 196  74
         DefaultVersionInfo version = null;
 197  74
         if ( digits != null )
 198  
         {
 199  68
             List<String> digits = new ArrayList<String>( this.digits );
 200  68
             String annotationRevision = this.annotationRevision;
 201  68
             if ( StringUtils.isNumeric( annotationRevision ) )
 202  
             {
 203  12
                 annotationRevision = incrementVersionString( annotationRevision );
 204  
             }
 205  
             else
 206  
             {
 207  56
                 digits.set( digits.size() - 1, incrementVersionString( (String) digits.get( digits.size() - 1 ) ) );
 208  
             }
 209  
 
 210  68
             version = new DefaultVersionInfo( digits, annotation, annotationRevision, buildSpecifier,
 211  
                                               annotationSeparator, annotationRevSeparator, buildSeparator );
 212  
         }
 213  74
         return version;
 214  
     }
 215  
 
 216  
     /**
 217  
      * Compares this {@link DefaultVersionInfo} to the supplied {@link DefaultVersionInfo}
 218  
      * to determine which version is greater.
 219  
      *
 220  
      * @param obj the comparison version
 221  
      * @return the comparison value
 222  
      * @throws IllegalArgumentException if the components differ between the objects or if either of the annotations can not be determined.
 223  
      */
 224  
     public int compareTo( VersionInfo obj )
 225  
     {
 226  128
         DefaultVersionInfo that = (DefaultVersionInfo) obj;
 227  
 
 228  
         int result;
 229  
         // TODO: this is a workaround for a bug in DefaultArtifactVersion - fix there - 1.01 < 1.01.01
 230  128
         if ( strVersion.startsWith( that.strVersion ) && !strVersion.equals( that.strVersion )
 231  
             && strVersion.charAt( that.strVersion.length() ) != '-' )
 232  
         {
 233  2
             result = 1;
 234  
         }
 235  126
         else if ( that.strVersion.startsWith( strVersion ) && !strVersion.equals( that.strVersion )
 236  
             && that.strVersion.charAt( strVersion.length() ) != '-' )
 237  
         {
 238  2
             result = -1;
 239  
         }
 240  
         else
 241  
         {
 242  
             // TODO: this is a workaround for a bug in DefaultArtifactVersion - fix there - it should not consider case in comparing the qualifier
 243  
             // NOTE: The combination of upper-casing and lower-casing is an approximation of String.equalsIgnoreCase()
 244  124
             String thisVersion = strVersion.toUpperCase( Locale.ENGLISH ).toLowerCase( Locale.ENGLISH );
 245  124
             String thatVersion = that.strVersion.toUpperCase( Locale.ENGLISH ).toLowerCase( Locale.ENGLISH );
 246  
 
 247  124
             result = new DefaultArtifactVersion( thisVersion ).compareTo( new DefaultArtifactVersion( thatVersion ) );
 248  
         }
 249  128
         return result;
 250  
     }
 251  
 
 252  
     public boolean equals( Object obj )
 253  
     {
 254  4
         if ( !( obj instanceof DefaultVersionInfo ) )
 255  
         {
 256  0
             return false;
 257  
         }
 258  
 
 259  4
         return compareTo( (VersionInfo) obj ) == 0;
 260  
     }
 261  
 
 262  
     /**
 263  
      * Takes a string and increments it as an integer.
 264  
      * Preserves any lpad of "0" zeros.
 265  
      *
 266  
      * @param s
 267  
      */
 268  
     protected String incrementVersionString( String s )
 269  
     {
 270  68
         int n = Integer.valueOf( s ).intValue() + 1;
 271  68
         String value = String.valueOf( n );
 272  68
         if ( value.length() < s.length() )
 273  
         {
 274  
             // String was left-padded with zeros
 275  10
             value = StringUtils.leftPad( value, s.length(), "0" );
 276  
         }
 277  68
         return value;
 278  
     }
 279  
 
 280  
     public String getSnapshotVersionString()
 281  
     {
 282  38
         if ( strVersion.equals( Artifact.SNAPSHOT_VERSION ) )
 283  
         {
 284  2
             return strVersion;
 285  
         }
 286  
 
 287  36
         String baseVersion = getReleaseVersionString();
 288  
 
 289  36
         if ( baseVersion.length() > 0 )
 290  
         {
 291  36
             baseVersion += "-";
 292  
         }
 293  
 
 294  36
         return baseVersion + Artifact.SNAPSHOT_VERSION;
 295  
     }
 296  
 
 297  
     public String getReleaseVersionString()
 298  
     {
 299  100
         String baseVersion = strVersion;
 300  
 
 301  100
         Matcher m = Artifact.VERSION_FILE_PATTERN.matcher( baseVersion );
 302  100
         if ( m.matches() )
 303  
         {
 304  4
             baseVersion = m.group( 1 );
 305  
         }
 306  
         // MRELEASE-623 SNAPSHOT is case-insensitive
 307  96
         else if ( StringUtils.right( baseVersion, 9 ).equalsIgnoreCase( "-" + Artifact.SNAPSHOT_VERSION ) )
 308  
         {
 309  54
             baseVersion = baseVersion.substring( 0, baseVersion.length() - Artifact.SNAPSHOT_VERSION.length() - 1 );
 310  
         }
 311  42
         else if ( baseVersion.equals( Artifact.SNAPSHOT_VERSION ) )
 312  
         {
 313  4
             baseVersion = "1.0";
 314  
         }
 315  100
         return baseVersion;
 316  
     }
 317  
 
 318  
     public String toString()
 319  
     {
 320  144
         return strVersion;
 321  
     }
 322  
 
 323  
     protected static String getVersionString( DefaultVersionInfo info, String buildSpecifier, String buildSeparator )
 324  
     {
 325  68
         StringBuilder sb = new StringBuilder();
 326  
 
 327  68
         if ( info.digits != null )
 328  
         {
 329  68
             sb.append( joinDigitString( info.digits ) );
 330  
         }
 331  
 
 332  68
         if ( StringUtils.isNotEmpty( info.annotation ) )
 333  
         {
 334  16
             sb.append( StringUtils.defaultString( info.annotationSeparator ) );
 335  16
             sb.append( info.annotation );
 336  
         }
 337  
 
 338  68
         if ( StringUtils.isNotEmpty( info.annotationRevision ) )
 339  
         {
 340  12
             if ( StringUtils.isEmpty( info.annotation ) )
 341  
             {
 342  0
                 sb.append( StringUtils.defaultString( info.annotationSeparator ) );
 343  
             }
 344  
             else
 345  
             {
 346  12
                 sb.append( StringUtils.defaultString( info.annotationRevSeparator ) );
 347  
             }
 348  12
             sb.append( info.annotationRevision );
 349  
         }
 350  
 
 351  68
         if ( StringUtils.isNotEmpty( buildSpecifier ) )
 352  
         {
 353  22
             sb.append( StringUtils.defaultString( buildSeparator ) );
 354  22
             sb.append( buildSpecifier );
 355  
         }
 356  
 
 357  68
         return sb.toString();
 358  
     }
 359  
 
 360  
     /**
 361  
      * Simply joins the items in the list with "." period
 362  
      *
 363  
      * @param digits
 364  
      */
 365  
     protected static String joinDigitString( List<String> digits )
 366  
     {
 367  130
         return digits != null ? StringUtils.join( digits.iterator(), DIGIT_SEPARATOR_STRING ) : null;
 368  
     }
 369  
 
 370  
     /**
 371  
      * Splits the string on "." and returns a list
 372  
      * containing each digit.
 373  
      *
 374  
      * @param strDigits
 375  
      */
 376  
     private List<String> parseDigits( String strDigits )
 377  
     {
 378  420
         return Arrays.asList( StringUtils.split( strDigits, DIGIT_SEPARATOR_STRING ) );
 379  
     }
 380  
 
 381  
     //--------------------------------------------------
 382  
     // Getters & Setters
 383  
     //--------------------------------------------------
 384  
 
 385  
     private static String nullIfEmpty( String s )
 386  
     {
 387  1156
         return StringUtils.isEmpty( s ) ? null : s;
 388  
     }
 389  
 
 390  
     public List<String> getDigits()
 391  
     {
 392  62
         return digits;
 393  
     }
 394  
 
 395  
     public String getAnnotation()
 396  
     {
 397  62
         return annotation;
 398  
     }
 399  
 
 400  
     public String getAnnotationRevision()
 401  
     {
 402  62
         return annotationRevision;
 403  
     }
 404  
 
 405  
     public String getBuildSpecifier()
 406  
     {
 407  62
         return buildSpecifier;
 408  
     }
 409  
 
 410  
 }