View Javadoc

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  public class HgVersion implements Comparable<HgVersion> {
16  
17      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      private HgVersion(String versionString) {
37          if (versionString == null) {
38              this.versionString = "unknown";
39              this.major = 0;
40              this.minor = 0;
41              this.release = 0;
42              this.releaseCandidate = false;
43              this.suffix = null;
44          } else {
45              this.versionString = versionString;
46              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              if (pos != 1 && pos != 2) {
51                  throw new IllegalArgumentException();
52              }
53              this.major = parseInt(versionString.substring(0, pos));
54              // Like major allow for 1 or 2 digit minor
55              String rest = versionString.substring(pos + 1);
56              pos = indexOfFirstNonDigit(rest);
57              this.minor = parseInt(rest.substring(0, pos));
58              if (rest.length() > pos && rest.charAt(pos) == '.') {
59                  rest = rest.substring(pos + 1);
60                  pos = indexOfFirstNonDigit(rest);
61                  this.release = Integer.valueOf(parseInt(rest.substring(0, pos)));
62              } else {
63                  this.release = null;
64              }
65              rest = rest.substring(pos);
66  
67              this.releaseCandidate = rest.startsWith("-rc+") || rest.equals("-rc");
68  
69              if (this.releaseCandidate) {
70                  rest = rest.substring(3);
71              }
72              if (rest.length() != 0) {
73                  if (rest.charAt(0) != '+') {
74                      throw new IllegalArgumentException();
75                  }
76                  this.suffix = rest.substring(1);
77              } else {
78                  this.suffix = null;
79              }
80          }
81      }
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          if (versionString == null) {
99              throw new IllegalArgumentException();
100         }
101         return new HgVersion(versionString);
102     }
103 
104     /**
105      * @return a HgVersion representing the unknow version
106      */
107     public static HgVersion unknown() {
108         return UNKNOWN;
109     }
110 
111     /**
112      * @return the String that this HgVersion was created from
113      */
114     public String getVersionString() {
115         return versionString;
116     }
117 
118     /**
119      * 
120      * @return the major (i.e. first) version number
121      */
122     public int getMajor() {
123         return major;
124     }
125 
126     /**
127      * 
128      * @return the minor (i.e. second) version number
129      */
130     public int getMinor() {
131         return minor;
132     }
133 
134     /**
135      * 
136      * @return the release (i.e. third and last) version number
137      */
138     public Integer getRelease() {
139         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         return releaseCandidate;
149     }
150 
151     /**
152      * 
153      * @return the suffix part
154      */
155     public String getSuffix() {
156         return suffix;
157     }
158 
159     /**
160      * 
161      * @return true if the receiver is a <em>unknown</em> instance.
162      */
163     public boolean isUnknown() {
164         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         if (that.equals(UNKNOWN) || this.equals(UNKNOWN)) {
174             throw new IllegalArgumentException();
175         }
176         // Note releaseCandidate parameters are first 'that' then
177         // 'this'
178         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         return this.compareTo(ver) < 0;
191     }
192 
193     @Override
194     public String toString() {
195         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         int length = s.length();
208         for (int i = 0; i < length; i++) {
209             if (!Character.isDigit(s.charAt(i))) {
210                 return i;
211             }
212         }
213         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         int length = s.length();
227         if (length == 0 || length > 2) {
228             throw new IllegalArgumentException();
229         }
230         int result = 0;
231         for (int i = 0; i < length; i++) {
232             if (Character.isDigit(s.charAt(i))) {
233                 result = result * 10 + s.charAt(i) - '0';
234             } else {
235                 throw new IllegalArgumentException();
236             }
237         }
238         return (byte) result;
239     }
240 
241 }