Coverage Report - org.apache.maven.artifact.versioning.VersionRange
 
Classes in this File Line Coverage Branch Coverage Complexity
VersionRange
0%
0/200
0%
0/184
5.176
 
 1  
 package org.apache.maven.artifact.versioning;
 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 java.util.ArrayList;
 23  
 import java.util.Collections;
 24  
 import java.util.Iterator;
 25  
 import java.util.List;
 26  
 
 27  
 import org.apache.maven.artifact.Artifact;
 28  
 
 29  
 /**
 30  
  * Construct a version range from a specification.
 31  
  *
 32  
  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
 33  
  * @version $Id: VersionRange.java 640549 2008-03-24 20:05:11Z bentmann $
 34  
  */
 35  
 public class VersionRange
 36  
 {
 37  
     private final ArtifactVersion recommendedVersion;
 38  
 
 39  
     private final List restrictions;
 40  
 
 41  
     private VersionRange( ArtifactVersion recommendedVersion, List restrictions )
 42  0
     {
 43  0
         this.recommendedVersion = recommendedVersion;
 44  0
         this.restrictions = restrictions;
 45  0
     }
 46  
 
 47  
     public ArtifactVersion getRecommendedVersion()
 48  
     {
 49  0
         return recommendedVersion;
 50  
     }
 51  
 
 52  
     public List getRestrictions()
 53  
     {
 54  0
         return restrictions;
 55  
     }
 56  
     
 57  
     public VersionRange cloneOf()
 58  
     {
 59  0
         List copiedRestrictions = null;
 60  
         
 61  0
         if ( restrictions != null )
 62  
         {
 63  0
             copiedRestrictions = new ArrayList();
 64  
             
 65  0
             if ( !restrictions.isEmpty() )
 66  
             {
 67  0
                 copiedRestrictions.addAll( restrictions );
 68  
             }
 69  
         }
 70  
         
 71  0
         return new VersionRange( recommendedVersion, copiedRestrictions );
 72  
     }
 73  
 
 74  
     /**
 75  
      * Create a version range from a string representation
 76  
      * 
 77  
      * Some spec examples are
 78  
      * <ul>
 79  
      *   <li><code>1.0</code> Version 1.0</li>
 80  
      *   <li><code>[1.0,2.0)</code> Versions 1.0 (included) to 2.0 (not included)</li>
 81  
      *   <li><code>[1.0,2.0]</code> Versions 1.0 to 2.0 (both included)</li>
 82  
      *   <li><code>[1.5,)</code> Versions 1.5 and higher</li>
 83  
      *   <li><code>(,1.0],[1.2,)</code> Versions up to 1.0 (included) and 1.2 or higher</li>
 84  
      * </ul>
 85  
      * 
 86  
      * @param spec string representation of a version or version range
 87  
      * @return a new {@link VersionRange} object that represents the spec
 88  
      * @throws InvalidVersionSpecificationException
 89  
      */
 90  
     public static VersionRange createFromVersionSpec( String spec )
 91  
         throws InvalidVersionSpecificationException
 92  
     {
 93  0
         if ( spec == null )
 94  
         {
 95  0
             return null;
 96  
         }
 97  
 
 98  0
         List restrictions = new ArrayList();
 99  0
         String process = spec;
 100  0
         ArtifactVersion version = null;
 101  0
         ArtifactVersion upperBound = null;
 102  0
         ArtifactVersion lowerBound = null;
 103  
 
 104  0
         while ( process.startsWith( "[" ) || process.startsWith( "(" ) )
 105  
         {
 106  0
             int index1 = process.indexOf( ")" );
 107  0
             int index2 = process.indexOf( "]" );
 108  
 
 109  0
             int index = index2;
 110  0
             if ( index2 < 0 || index1 < index2 )
 111  
             {
 112  0
                 if ( index1 >= 0 )
 113  
                 {
 114  0
                     index = index1;
 115  
                 }
 116  
             }
 117  
 
 118  0
             if ( index < 0 )
 119  
             {
 120  0
                 throw new InvalidVersionSpecificationException( "Unbounded range: " + spec );
 121  
             }
 122  
 
 123  0
             Restriction restriction = parseRestriction( process.substring( 0, index + 1 ) );
 124  0
             if ( lowerBound == null )
 125  
             {
 126  0
                 lowerBound = restriction.getLowerBound();
 127  
             }
 128  0
             if ( upperBound != null )
 129  
             {
 130  0
                 if ( restriction.getLowerBound() == null || restriction.getLowerBound().compareTo( upperBound ) < 0 )
 131  
                 {
 132  0
                     throw new InvalidVersionSpecificationException( "Ranges overlap: " + spec );
 133  
                 }
 134  
             }
 135  0
             restrictions.add( restriction );
 136  0
             upperBound = restriction.getUpperBound();
 137  
 
 138  0
             process = process.substring( index + 1 ).trim();
 139  
 
 140  0
             if ( process.length() > 0 && process.startsWith( "," ) )
 141  
             {
 142  0
                 process = process.substring( 1 ).trim();
 143  
             }
 144  0
         }
 145  
 
 146  0
         if ( process.length() > 0 )
 147  
         {
 148  0
             if ( restrictions.size() > 0 )
 149  
             {
 150  0
                 throw new InvalidVersionSpecificationException(
 151  
                     "Only fully-qualified sets allowed in multiple set scenario: " + spec );
 152  
             }
 153  
             else
 154  
             {
 155  0
                 version = new DefaultArtifactVersion( process );
 156  0
                 restrictions.add( Restriction.EVERYTHING );
 157  
             }
 158  
         }
 159  
 
 160  0
         return new VersionRange( version, restrictions );
 161  
     }
 162  
 
 163  
     private static Restriction parseRestriction( String spec )
 164  
         throws InvalidVersionSpecificationException
 165  
     {
 166  0
         boolean lowerBoundInclusive = spec.startsWith( "[" );
 167  0
         boolean upperBoundInclusive = spec.endsWith( "]" );
 168  
 
 169  0
         String process = spec.substring( 1, spec.length() - 1 ).trim();
 170  
 
 171  
         Restriction restriction;
 172  
 
 173  0
         int index = process.indexOf( "," );
 174  
 
 175  0
         if ( index < 0 )
 176  
         {
 177  0
             if ( !lowerBoundInclusive || !upperBoundInclusive )
 178  
             {
 179  0
                 throw new InvalidVersionSpecificationException( "Single version must be surrounded by []: " + spec );
 180  
             }
 181  
 
 182  0
             ArtifactVersion version = new DefaultArtifactVersion( process );
 183  
 
 184  0
             restriction = new Restriction( version, lowerBoundInclusive, version, upperBoundInclusive );
 185  0
         }
 186  
         else
 187  
         {
 188  0
             String lowerBound = process.substring( 0, index ).trim();
 189  0
             String upperBound = process.substring( index + 1 ).trim();
 190  0
             if ( lowerBound.equals( upperBound ) )
 191  
             {
 192  0
                 throw new InvalidVersionSpecificationException( "Range cannot have identical boundaries: " + spec );
 193  
             }
 194  
 
 195  0
             ArtifactVersion lowerVersion = null;
 196  0
             if ( lowerBound.length() > 0 )
 197  
             {
 198  0
                 lowerVersion = new DefaultArtifactVersion( lowerBound );
 199  
             }
 200  0
             ArtifactVersion upperVersion = null;
 201  0
             if ( upperBound.length() > 0 )
 202  
             {
 203  0
                 upperVersion = new DefaultArtifactVersion( upperBound );
 204  
             }
 205  
 
 206  0
             if ( upperVersion != null && lowerVersion != null && upperVersion.compareTo( lowerVersion ) < 0 )
 207  
             {
 208  0
                 throw new InvalidVersionSpecificationException( "Range defies version ordering: " + spec );
 209  
             }
 210  
 
 211  0
             restriction = new Restriction( lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive );
 212  
         }
 213  
 
 214  0
         return restriction;
 215  
     }
 216  
 
 217  
     public static VersionRange createFromVersion( String version )
 218  
     {
 219  0
         return new VersionRange( new DefaultArtifactVersion( version ), Collections.EMPTY_LIST );
 220  
     }
 221  
 
 222  
     /**
 223  
      * Creates and returns a new <code>VersionRange</code> that is a restriction of this 
 224  
      * version range and the specified version range.
 225  
      * <p>
 226  
      * Note: Precedence is given to the recommended version from this version range over the 
 227  
      * recommended version from the specified version range.
 228  
      * </p>
 229  
      * @param restriction the <code>VersionRange</code> that will be used to restrict this version
 230  
      * range.
 231  
      * @return the <code>VersionRange</code> that is a restriction of this version range and the 
 232  
      * specified version range.
 233  
      * <p>
 234  
      * The restrictions of the returned version range will be an intersection of the restrictions
 235  
      * of this version range and the specified version range if both version ranges have 
 236  
      * restrictions. Otherwise, the restrictions on the returned range will be empty.
 237  
      * </p>
 238  
      * <p>
 239  
      * The recommended version of the returned version range will be the recommended version of
 240  
      * this version range, provided that ranges falls within the intersected restrictions. If 
 241  
      * the restrictions are empty, this version range's recommended version is used if it is not
 242  
      * <code>null</code>. If it is <code>null</code>, the specified version range's recommended
 243  
      * version is used (provided it is non-<code>null</code>). If no recommended version can be 
 244  
      * obtained, the returned version range's recommended version is set to <code>null</code>.
 245  
      * </p>
 246  
      * @throws NullPointerException if the specified <code>VersionRange</code> is 
 247  
      * <code>null</code>.
 248  
      */
 249  
     public VersionRange restrict( VersionRange restriction )
 250  
     {
 251  0
         List r1 = this.restrictions;
 252  0
         List r2 = restriction.restrictions;
 253  
         List restrictions;
 254  0
         if ( r1.isEmpty() || r2.isEmpty() )
 255  
         {
 256  0
             restrictions = Collections.EMPTY_LIST;
 257  
         }
 258  
         else
 259  
         {
 260  0
             restrictions = intersection( r1, r2 );
 261  
         }
 262  
 
 263  0
         ArtifactVersion version = null;
 264  0
         if ( restrictions.size() > 0 )
 265  
         {
 266  0
             boolean found = false;
 267  0
             for ( Iterator i = restrictions.iterator(); i.hasNext() && !found; )
 268  
             {
 269  0
                 Restriction r = (Restriction) i.next();
 270  
 
 271  0
                 if ( recommendedVersion != null && r.containsVersion( recommendedVersion ) )
 272  
                 {
 273  
                     // if we find the original, use that
 274  0
                     version = recommendedVersion;
 275  0
                     found = true;
 276  
                 }
 277  0
                 else if ( version == null && restriction.getRecommendedVersion() != null &&
 278  
                     r.containsVersion( restriction.getRecommendedVersion() ) )
 279  
                 {
 280  
                     // use this if we can, but prefer the original if possible
 281  0
                     version = restriction.getRecommendedVersion();
 282  
                 }
 283  0
             }
 284  0
         }
 285  
         // Either the original or the specified version ranges have no restructions
 286  0
         else if ( recommendedVersion != null )
 287  
         {
 288  
             // Use the original recommended version since it exists
 289  0
             version = recommendedVersion;
 290  
         }
 291  0
         else if (restriction.recommendedVersion != null)
 292  
         {
 293  
             // Use the recommended version from the specified VersionRange since there is no
 294  
             // original recommended version
 295  0
             version = restriction.recommendedVersion;
 296  
         }
 297  
 /* TODO: should throw this immediately, but need artifact
 298  
         else
 299  
         {
 300  
             throw new OverConstrainedVersionException( "Restricting incompatible version ranges" );
 301  
         }
 302  
 */
 303  
 
 304  0
         return new VersionRange( version, restrictions );
 305  
     }
 306  
 
 307  
     private List intersection( List r1, List r2 )
 308  
     {
 309  0
         List restrictions = new ArrayList( r1.size() + r2.size() );
 310  0
         Iterator i1 = r1.iterator();
 311  0
         Iterator i2 = r2.iterator();
 312  0
         Restriction res1 = (Restriction) i1.next();
 313  0
         Restriction res2 = (Restriction) i2.next();
 314  
 
 315  0
         boolean done = false;
 316  0
         while ( !done )
 317  
         {
 318  0
             if ( res1.getLowerBound() == null || res2.getUpperBound() == null ||
 319  
                 res1.getLowerBound().compareTo( res2.getUpperBound() ) <= 0 )
 320  
             {
 321  0
                 if ( res1.getUpperBound() == null || res2.getLowerBound() == null ||
 322  
                     res1.getUpperBound().compareTo( res2.getLowerBound() ) >= 0 )
 323  
                 {
 324  
                     ArtifactVersion lower;
 325  
                     ArtifactVersion upper;
 326  
                     boolean lowerInclusive;
 327  
                     boolean upperInclusive;
 328  
 
 329  
                     // overlaps
 330  0
                     if ( res1.getLowerBound() == null )
 331  
                     {
 332  0
                         lower = res2.getLowerBound();
 333  0
                         lowerInclusive = res2.isLowerBoundInclusive();
 334  
                     }
 335  0
                     else if ( res2.getLowerBound() == null )
 336  
                     {
 337  0
                         lower = res1.getLowerBound();
 338  0
                         lowerInclusive = res1.isLowerBoundInclusive();
 339  
                     }
 340  
                     else
 341  
                     {
 342  0
                         int comparison = res1.getLowerBound().compareTo( res2.getLowerBound() );
 343  0
                         if ( comparison < 0 )
 344  
                         {
 345  0
                             lower = res2.getLowerBound();
 346  0
                             lowerInclusive = res2.isLowerBoundInclusive();
 347  
                         }
 348  0
                         else if ( comparison == 0 )
 349  
                         {
 350  0
                             lower = res1.getLowerBound();
 351  0
                             lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
 352  
                         }
 353  
                         else
 354  
                         {
 355  0
                             lower = res1.getLowerBound();
 356  0
                             lowerInclusive = res1.isLowerBoundInclusive();
 357  
                         }
 358  
                     }
 359  
 
 360  0
                     if ( res1.getUpperBound() == null )
 361  
                     {
 362  0
                         upper = res2.getUpperBound();
 363  0
                         upperInclusive = res2.isUpperBoundInclusive();
 364  
                     }
 365  0
                     else if ( res2.getUpperBound() == null )
 366  
                     {
 367  0
                         upper = res1.getUpperBound();
 368  0
                         upperInclusive = res1.isUpperBoundInclusive();
 369  
                     }
 370  
                     else
 371  
                     {
 372  0
                         int comparison = res1.getUpperBound().compareTo( res2.getUpperBound() );
 373  0
                         if ( comparison < 0 )
 374  
                         {
 375  0
                             upper = res1.getUpperBound();
 376  0
                             upperInclusive = res1.isUpperBoundInclusive();
 377  
                         }
 378  0
                         else if ( comparison == 0 )
 379  
                         {
 380  0
                             upper = res1.getUpperBound();
 381  0
                             upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
 382  
                         }
 383  
                         else
 384  
                         {
 385  0
                             upper = res2.getUpperBound();
 386  0
                             upperInclusive = res2.isUpperBoundInclusive();
 387  
                         }
 388  
                     }
 389  
 
 390  
                     // don't add if they are equal and one is not inclusive
 391  0
                     if ( lower == null || upper == null || lower.compareTo( upper ) != 0 )
 392  
                     {
 393  0
                         restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
 394  
                     }
 395  0
                     else if ( lowerInclusive && upperInclusive )
 396  
                     {
 397  0
                         restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
 398  
                     }
 399  
 
 400  
                     //noinspection ObjectEquality
 401  0
                     if ( upper == res2.getUpperBound() )
 402  
                     {
 403  
                         // advance res2
 404  0
                         if ( i2.hasNext() )
 405  
                         {
 406  0
                             res2 = (Restriction) i2.next();
 407  
                         }
 408  
                         else
 409  
                         {
 410  0
                             done = true;
 411  
                         }
 412  
                     }
 413  
                     else
 414  
                     {
 415  
                         // advance res1
 416  0
                         if ( i1.hasNext() )
 417  
                         {
 418  0
                             res1 = (Restriction) i1.next();
 419  
                         }
 420  
                         else
 421  
                         {
 422  0
                             done = true;
 423  
                         }
 424  
                     }
 425  0
                 }
 426  
                 else
 427  
                 {
 428  
                     // move on to next in r1
 429  0
                     if ( i1.hasNext() )
 430  
                     {
 431  0
                         res1 = (Restriction) i1.next();
 432  
                     }
 433  
                     else
 434  
                     {
 435  0
                         done = true;
 436  
                     }
 437  
                 }
 438  
             }
 439  
             else
 440  
             {
 441  
                 // move on to next in r2
 442  0
                 if ( i2.hasNext() )
 443  
                 {
 444  0
                     res2 = (Restriction) i2.next();
 445  
                 }
 446  
                 else
 447  
                 {
 448  0
                     done = true;
 449  
                 }
 450  
             }
 451  
         }
 452  
 
 453  0
         return restrictions;
 454  
     }
 455  
 
 456  
     public ArtifactVersion getSelectedVersion( Artifact artifact )
 457  
         throws OverConstrainedVersionException
 458  
     {
 459  
         ArtifactVersion version;
 460  0
         if ( recommendedVersion != null )
 461  
         {
 462  0
             version = recommendedVersion;
 463  
         }
 464  
         else
 465  
         {
 466  0
             if ( restrictions.size() == 0 )
 467  
             {
 468  0
                 throw new OverConstrainedVersionException( "The artifact has no valid ranges", artifact );
 469  
             }
 470  
 
 471  0
             version = null;
 472  
         }
 473  0
         return version;
 474  
     }
 475  
 
 476  
     public boolean isSelectedVersionKnown( Artifact artifact )
 477  
         throws OverConstrainedVersionException
 478  
     {
 479  0
         boolean value = false;
 480  0
         if ( recommendedVersion != null )
 481  
         {
 482  0
             value = true;
 483  
         }
 484  
         else
 485  
         {
 486  0
             if ( restrictions.size() == 0 )
 487  
             {
 488  0
                 throw new OverConstrainedVersionException( "The artifact has no valid ranges", artifact );
 489  
             }
 490  
         }
 491  0
         return value;
 492  
     }
 493  
 
 494  
     public String toString()
 495  
     {
 496  0
         if ( recommendedVersion != null )
 497  
         {
 498  0
             return recommendedVersion.toString();
 499  
         }
 500  
         else
 501  
         {
 502  0
             StringBuffer buf = new StringBuffer();
 503  0
             for ( Iterator i = restrictions.iterator(); i.hasNext(); )
 504  
             {
 505  0
                 Restriction r = (Restriction) i.next();
 506  
 
 507  0
                 buf.append( r.toString() );
 508  
 
 509  0
                 if ( i.hasNext() )
 510  
                 {
 511  0
                     buf.append( "," );
 512  
                 }
 513  0
             }
 514  0
             return buf.toString();
 515  
         }
 516  
     }
 517  
 
 518  
     public ArtifactVersion matchVersion( List versions )
 519  
     {
 520  
         // TODO: could be more efficient by sorting the list and then moving along the restrictions in order?
 521  
 
 522  0
         ArtifactVersion matched = null;
 523  0
         for ( Iterator i = versions.iterator(); i.hasNext(); )
 524  
         {
 525  0
             ArtifactVersion version = (ArtifactVersion) i.next();
 526  0
             if ( containsVersion( version ) )
 527  
             {
 528  
                 // valid - check if it is greater than the currently matched version
 529  0
                 if ( matched == null || version.compareTo( matched ) > 0 )
 530  
                 {
 531  0
                     matched = version;
 532  
                 }
 533  
             }
 534  0
         }
 535  0
         return matched;
 536  
     }
 537  
 
 538  
     public boolean containsVersion( ArtifactVersion version )
 539  
     {
 540  0
         for ( Iterator i = restrictions.iterator(); i.hasNext(); )
 541  
         {
 542  0
             Restriction restriction = (Restriction) i.next();
 543  0
             if ( restriction.containsVersion( version ) )
 544  
             {
 545  0
                 return true;
 546  
             }
 547  0
         }
 548  0
         return false;
 549  
     }
 550  
 
 551  
     public boolean hasRestrictions()
 552  
     {
 553  0
         return !restrictions.isEmpty() && recommendedVersion == null;
 554  
     }
 555  
 
 556  
     public boolean equals( Object obj )
 557  
     {
 558  0
         if (this == obj){
 559  0
             return true;
 560  
         }
 561  0
         if (!(obj instanceof VersionRange ))
 562  
         {
 563  0
             return false;
 564  
         }
 565  0
         VersionRange other = (VersionRange) obj;
 566  
         
 567  0
         boolean equals =
 568  
             recommendedVersion == other.recommendedVersion ||
 569  
                 ( ( recommendedVersion != null ) && recommendedVersion.equals( other.recommendedVersion ) );
 570  0
         equals &=
 571  
             restrictions == other.restrictions ||
 572  
                 ( ( restrictions != null ) && restrictions.equals( other.restrictions ) );
 573  0
         return equals;
 574  
     }
 575  
 
 576  
     public int hashCode()
 577  
     {
 578  0
         int hash = 7;
 579  0
         hash = 31 * hash + ( recommendedVersion == null ? 0 : recommendedVersion.hashCode() );
 580  0
         hash = 31 * hash + ( restrictions == null ? 0 : restrictions.hashCode() );
 581  0
         return hash;
 582  
     }
 583  
 }