Coverage Report - com.aragost.javahg.HgVersion
 
Classes in this File Line Coverage Branch Coverage Complexity
HgVersion
95%
60/63
90%
36/40
2.667
 
 1  
 package com.aragost.javahg;
 2  
 
 3  
 import com.google.common.collect.ComparisonChain;
 4  
 import com.google.common.collect.Ordering;
 5  
 
 6  
 /**
 7  
  * Representing a Mercurial version.
 8  
  * <p>
 9  
  * The version has the format
 10  
  * [major].[minor]{.[release]}{-rc}{+[suffix]} where parts enclosed in
 11  
  * {...} is optional. The major, minor, and release part can at most
 12  
  * be 2 digits, the suffix part can be any string.
 13  
  * 
 14  
  */
 15  0
 public class HgVersion implements Comparable<HgVersion> {
 16  
 
 17  1
     private static final HgVersion UNKNOWN = new HgVersion(null);
 18  
 
 19  
     private final String versionString;
 20  
 
 21  
     private final byte major;
 22  
 
 23  
     private final byte minor;
 24  
 
 25  
     private final Integer release;
 26  
 
 27  
     private final boolean releaseCandidate;
 28  
 
 29  
     private final String suffix;
 30  
 
 31  
     /**
 32  
      * Private constuctor, use factory method to create an instance.
 33  
      * 
 34  
      * @param versionString
 35  
      */
 36  72
     private HgVersion(String versionString) {
 37  72
         if (versionString == null) {
 38  1
             this.versionString = "unknown";
 39  1
             this.major = 0;
 40  1
             this.minor = 0;
 41  1
             this.release = 0;
 42  1
             this.releaseCandidate = false;
 43  1
             this.suffix = null;
 44  
         } else {
 45  71
             this.versionString = versionString;
 46  71
             int pos = versionString.indexOf('.');
 47  
             // All current versions number as 2.1 has major number 1
 48  
             // digit
 49  
             // but we allow for 2
 50  71
             if (pos != 1 && pos != 2) {
 51  2
                 throw new IllegalArgumentException();
 52  
             }
 53  69
             this.major = parseInt(versionString.substring(0, pos));
 54  
             // Like major allow for 1 or 2 digit minor
 55  65
             String rest = versionString.substring(pos + 1);
 56  65
             pos = indexOfFirstNonDigit(rest);
 57  65
             this.minor = parseInt(rest.substring(0, pos));
 58  62
             if (rest.length() > pos && rest.charAt(pos) == '.') {
 59  17
                 rest = rest.substring(pos + 1);
 60  17
                 pos = indexOfFirstNonDigit(rest);
 61  17
                 this.release = Integer.valueOf(parseInt(rest.substring(0, pos)));
 62  
             } else {
 63  45
                 this.release = null;
 64  
             }
 65  59
             rest = rest.substring(pos);
 66  
 
 67  59
             this.releaseCandidate = rest.startsWith("-rc+") || rest.equals("-rc");
 68  
 
 69  59
             if (this.releaseCandidate) {
 70  11
                 rest = rest.substring(3);
 71  
             }
 72  59
             if (rest.length() != 0) {
 73  36
                 if (rest.charAt(0) != '+') {
 74  3
                     throw new IllegalArgumentException();
 75  
                 }
 76  33
                 this.suffix = rest.substring(1);
 77  
             } else {
 78  23
                 this.suffix = null;
 79  
             }
 80  
         }
 81  57
     }
 82  
 
 83  
     /**
 84  
      * Factory method to create a HgVersion from a version string from
 85  
      * Mercurial
 86  
      * <p>
 87  
      * Examples of valid input:
 88  
      * <ul>
 89  
      * <li>2.1</li>
 90  
      * <li>1.9.3</li>
 91  
      * <li>2.0.1+20120221</li>
 92  
      * <ul>
 93  
      * 
 94  
      * @param versionString
 95  
      * @return
 96  
      */
 97  
     public static HgVersion fromString(String versionString) {
 98  71
         if (versionString == null) {
 99  0
             throw new IllegalArgumentException();
 100  
         }
 101  71
         return new HgVersion(versionString);
 102  
     }
 103  
 
 104  
     /**
 105  
      * @return a HgVersion representing the unknow version
 106  
      */
 107  
     public static HgVersion unknown() {
 108  1
         return UNKNOWN;
 109  
     }
 110  
 
 111  
     /**
 112  
      * @return the String that this HgVersion was created from
 113  
      */
 114  
     public String getVersionString() {
 115  2
         return versionString;
 116  
     }
 117  
 
 118  
     /**
 119  
      * 
 120  
      * @return the major (i.e. first) version number
 121  
      */
 122  
     public int getMajor() {
 123  19
         return major;
 124  
     }
 125  
 
 126  
     /**
 127  
      * 
 128  
      * @return the minor (i.e. second) version number
 129  
      */
 130  
     public int getMinor() {
 131  18
         return minor;
 132  
     }
 133  
 
 134  
     /**
 135  
      * 
 136  
      * @return the release (i.e. third and last) version number
 137  
      */
 138  
     public Integer getRelease() {
 139  18
         return release;
 140  
     }
 141  
 
 142  
     /**
 143  
      * 
 144  
      * @return true if the version number is a relase candidate,
 145  
      *         otherwise false
 146  
      */
 147  
     public boolean isReleaseCandidate() {
 148  18
         return releaseCandidate;
 149  
     }
 150  
 
 151  
     /**
 152  
      * 
 153  
      * @return the suffix part
 154  
      */
 155  
     public String getSuffix() {
 156  18
         return suffix;
 157  
     }
 158  
 
 159  
     /**
 160  
      * 
 161  
      * @return true if the receiver is a <em>unknown</em> instance.
 162  
      */
 163  
     public boolean isUnknown() {
 164  2
         return this.equals(UNKNOWN);
 165  
     }
 166  
 
 167  
     /**
 168  
      * Compare this to the other version.
 169  
      * <p>
 170  
      * All parts of the version is used except the suffix part.
 171  
      */
 172  
     public int compareTo(HgVersion that) {
 173  24
         if (that.equals(UNKNOWN) || this.equals(UNKNOWN)) {
 174  0
             throw new IllegalArgumentException();
 175  
         }
 176  
         // Note releaseCandidate parameters are first 'that' then
 177  
         // 'this'
 178  24
         return ComparisonChain.start().compare(this.major, that.major).compare(this.minor, that.minor).compare(
 179  
                 this.release, that.release, Ordering.natural().nullsFirst()).compare(that.releaseCandidate,
 180  
                 this.releaseCandidate).result();
 181  
     }
 182  
 
 183  
     /**
 184  
      * 
 185  
      * @param ver
 186  
      * @return true if the receiver is strictly an earlier version
 187  
      *         than the argument
 188  
      */
 189  
     public boolean isBefore(HgVersion ver) {
 190  10
         return this.compareTo(ver) < 0;
 191  
     }
 192  
 
 193  
     @Override
 194  
     public String toString() {
 195  1
         return this.versionString;
 196  
     }
 197  
 
 198  
     /**
 199  
      * Return the index of the first charecter that isn't a digit. If
 200  
      * all characters is a digit then the length of the String is
 201  
      * returned.
 202  
      * 
 203  
      * @param s
 204  
      * @return
 205  
      */
 206  
     private int indexOfFirstNonDigit(String s) {
 207  82
         int length = s.length();
 208  171
         for (int i = 0; i < length; i++) {
 209  150
             if (!Character.isDigit(s.charAt(i))) {
 210  61
                 return i;
 211  
             }
 212  
         }
 213  21
         return length;
 214  
     }
 215  
 
 216  
     /**
 217  
      * Parse the specified String as an integer and return it as a
 218  
      * byte.
 219  
      * <p>
 220  
      * The String must have 1 or 2 characters and contain only digits.
 221  
      * 
 222  
      * @param s
 223  
      * @return
 224  
      */
 225  
     private byte parseInt(String s) {
 226  151
         int length = s.length();
 227  151
         if (length == 0 || length > 2) {
 228  6
             throw new IllegalArgumentException();
 229  
         }
 230  145
         int result = 0;
 231  310
         for (int i = 0; i < length; i++) {
 232  169
             if (Character.isDigit(s.charAt(i))) {
 233  165
                 result = result * 10 + s.charAt(i) - '0';
 234  
             } else {
 235  4
                 throw new IllegalArgumentException();
 236  
             }
 237  
         }
 238  141
         return (byte) result;
 239  
     }
 240  
 
 241  
 }