1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2011 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  /**
26   * Version is an immutable representation of dotted "number" consisting of 1 to 4 parts.
27   * 
28   * <p>
29   * Here is the grammar for version strings:
30   * </p>
31   * <pre>
32   * version ::= major('.'minor('.'micro('.'nano)?)?)?
33   * major ::= [0-9]+
34   * minor ::= [0-9]+
35   * micro ::= [0-9]+
36   * nano  ::= [0-9]+
37   * </pre>
38   * 
39   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
40   * @author DM Smith
41   */
42  public class Version implements Comparable<Version> {
43      public static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)(?:.(\\d+))?(?:.(\\d+))?(?:.(\\d+))?$");
44  
45      /**
46       * Created a version identifier from the specified string.
47       * 
48       * @param version String representation of the version identifier.
49       * @throws IllegalArgumentException If <code>version</code> is improperly
50       *         formatted.
51       */
52      public Version(String version) {
53          if (version == null) {
54              throw new IllegalArgumentException("Null version not allowed.");
55          }
56          this.original = version;
57          this.parts = new int[] { -1, -1, -1, -1 };
58          Matcher matcher = VERSION_PATTERN.matcher(this.original);
59          if (matcher.matches()) {
60              int count = matcher.groupCount();
61              for (int i = 1; i <= count; i++) {
62                  String part = matcher.group(i);
63                  if (part == null) {
64                      break;
65                  }
66                  parts[i - 1] = Integer.parseInt(part);
67              }
68          } else {
69              throw new IllegalArgumentException("invalid: " + version);
70          }
71      }
72  
73      /**
74       * Returns the original string representation of this version identifier.
75       *
76       * @return The original string representation of this version identifier.
77       */
78      @Override
79      public String toString() {
80          return original;
81      }
82  
83      @Override
84      public int hashCode() {
85          return original.hashCode();
86      }
87  
88      /**
89       * Compares this <code>Version</code> object to another object.
90       * 
91       * <p>
92       * A version is considered to be equal to another version if all the
93       * parts are equal.</p>
94       * 
95       * @param object The <code>Version</code> object to be compared.
96       * @return true if the two objects are equal.
97       */
98      @Override
99      public boolean equals(Object object) {
100         if (!(object instanceof Version)) {
101             return false;
102         }
103 
104         Version that = (Version) object;
105         if (that == this) {
106             return true;
107         }
108 
109         for (int i = 0; i < parts.length; i++) {
110             if (parts[i] != that.parts[i]) {
111                 return false;
112             }
113         }
114 
115         return true;
116     }
117 
118     /**
119      * Compares this <code>Version</code> object to another object.
120      * 
121      * <p>
122      * The comparison considers each of the parts (major, minor, micro, nano) in turn,
123      * comparing like with like. At any point the comparison is not equal, a result is
124      * known.
125      * </p>
126      * 
127      * @param object The <code>Version</code> object to be compared.
128      * @return A negative integer, zero, or a positive integer if this object is
129      *         less than, equal to, or greater than the specified
130      *         <code>Version</code> object.
131      * @throws ClassCastException If the specified object is not a <code>Version</code>.
132      */
133     public int compareTo(Version object) {
134         if (object == this) {
135             return 0;
136         }
137 
138         for (int i = 0; i < parts.length; i++) {
139             int result = parts[i] - object.parts[i];
140             if (result != 0) {
141                 return result;
142             }
143         }
144 
145         return 0;
146     }
147 
148     private final String         original;
149     private final int[]          parts;
150 
151 }
152