View Javadoc
1   /**
2    * This file Copyright (c) 2003-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.model;
35  
36  import java.util.regex.Pattern;
37  
38  /**
39   * Represents a module version. Format is x.y.z-classifier. y,z and classifier are
40   * optional. The classifier string is ignored in version comparisons.
41   */
42  public class Version {
43      private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Version.class);
44  
45      public static final Version UNDEFINED_FROM = new UndefinedEarlierVersion();
46      public static final Version UNDEFINED_TO = new UndefinedLaterVersion();
47      public static final Version UNDEFINED_DEVELOPMENT_VERSION = new UndefinedDevelopmentVersion();
48  
49      private static final Pattern classifierValidation = Pattern.compile("[A-Za-z0-9-_]+");
50      private final short major;
51      private final short minor;
52      private final short patch;
53      private final String classifier;
54  
55      /**
56       * Convenience constructor that could be used to register Deltas or update tasks.
57       */
58      protected Version(int major, int minor, int patch) {
59          this.major = (short) major;
60          this.minor = (short) minor;
61          this.patch = (short) patch;
62          this.classifier = null;
63      }
64  
65      private Version(String versionStr) {
66          final String numbers;
67          final int classifierIdx = versionStr.indexOf('-');
68          if (classifierIdx > 0) {
69              classifier = versionStr.substring(classifierIdx + 1);
70              if (!classifierValidation.matcher(classifier).matches()) {
71                  throw new IllegalArgumentException("Invalid classifier: \"" + classifier + "\" in version \"" + versionStr + "\"");
72              }
73              numbers = versionStr.substring(0, classifierIdx);
74          } else {
75              classifier = null;
76              numbers = versionStr;
77          }
78  
79          final String[] strings = numbers.split("\\.", -1);
80          if (strings.length > 0) {
81              major = getShortFor("major revision", versionStr, strings[0]);
82          } else {
83              major = getShortFor("major revision", versionStr, versionStr);
84          }
85          if (strings.length > 1) {
86              minor = getShortFor("minor revision", versionStr, strings[1]);
87          } else {
88              minor = 0;
89          }
90          if (strings.length > 2) {
91              patch = getShortFor("patch revision", versionStr, strings[2]);
92          } else {
93              patch = 0;
94          }
95      }
96  
97      /**
98       * Factory method that will parse a version string and return the correct Version implementation.
99       * @param versionStr version as string, for example <code>1.2.3-test</code>. The String
100      * <code>${project.version}</code> is interpreted as an undefined version during development ant it will always
101      * match version ranges
102      * @return a Version implementation, never null
103      */
104     public static Version parseVersion(String versionStr) {
105 
106         versionStr = versionStr.trim();
107 
108         log.debug("parsing version [{}]", versionStr);
109 
110         if (UndefinedDevelopmentVersion.isDevelopmentVersion(versionStr)) {
111             // development mode.
112             return UNDEFINED_DEVELOPMENT_VERSION;
113         }
114 
115         return new Version(versionStr);
116     }
117 
118     public static Version parseVersion(int major, int minor, int patch) {
119         return new Version(major, minor, patch);
120     }
121 
122     /**
123      * Compares major, minor and patch revisions of this Version against the given Version.
124      * Classifier is ignored.
125      */
126     public boolean isEquivalent(final Version other) {
127         if(other == UNDEFINED_DEVELOPMENT_VERSION){
128             return true;
129         }
130         return this.getMajor() == other.getMajor() &&
131                 this.getMinor() == other.getMinor() &&
132                 this.getPatch() == other.getPatch();
133     }
134 
135     public boolean isStrictlyAfter(final Version other) {
136         if(isEquivalent(other)){
137             return false;
138         }
139         if (this.getMajor() != other.getMajor()) {
140             return this.getMajor() > other.getMajor();
141         }
142         if (this.getMinor() != other.getMinor()) {
143             return this.getMinor() > other.getMinor();
144         }
145         if (this.getPatch() != other.getPatch()) {
146             return this.getPatch() > other.getPatch();
147         }
148         return false;
149     }
150 
151     public boolean isBeforeOrEquivalent(final Version other) {
152         return !isStrictlyAfter(other);
153     }
154 
155     public short getMajor() {
156         return major;
157     }
158 
159     public short getMinor() {
160         return minor;
161     }
162 
163     public short getPatch() {
164         return patch;
165     }
166 
167     public String getClassifier() {
168         return classifier;
169     }
170 
171     @Override
172     public String toString() {
173         return major + "." + minor + "." + patch + (classifier != null ? "-" + classifier : "");
174     }
175 
176     private short getShortFor(String message, String versionStr, String input) {
177         try {
178             return Short.parseShort(input);
179         } catch (NumberFormatException e) {
180             throw new RuntimeException("Invalid " + message + ": \"" + input + "\" in version \"" + versionStr + "\"");
181         }
182     }
183 
184     private static final class UndefinedLaterVersion extends Version {
185         public UndefinedLaterVersion() {
186             super(Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE);
187         }
188 
189         @Override
190         public String toString() {
191             return "*";
192         }
193     }
194 
195     private static final class UndefinedEarlierVersion extends Version {
196         public UndefinedEarlierVersion() {
197             super(Short.MIN_VALUE, Short.MIN_VALUE, Short.MIN_VALUE);
198         }
199 
200         @Override
201         public String toString() {
202             return "*";
203         }
204     }
205 
206     /**
207      * A undefined developer version being always equivalent to other versions. Any version in the module xml starting
208      * with <code>${</code> like <code>${project.version}</code> or <code>${buildNumber}</code>
209      */
210     static final class UndefinedDevelopmentVersion extends Version {
211 
212         @Deprecated
213         static final String KEY = "${project.version}";
214 
215         public UndefinedDevelopmentVersion() {
216             super(0, 0, 0);
217         }
218 
219         @Override
220         public boolean isEquivalent(Version other) {
221             return true;
222         }
223 
224         @Override
225         public String toString() {
226             return KEY;
227         }
228 
229         public static boolean isDevelopmentVersion(String version) {
230             return version != null && version.startsWith("${");
231         }
232 
233     }
234 
235     // generated methods:
236     @Override
237     public boolean equals(Object o) {
238         if (this == o) {
239             return true;
240         }
241         if (o == null || getClass() != o.getClass()) {
242             return false;
243         }
244 
245         Version version = (Version) o;
246 
247         if (major != version.major) {
248             return false;
249         }
250         if (minor != version.minor) {
251             return false;
252         }
253         if (patch != version.patch) {
254             return false;
255         }
256         if (classifier != null ? !classifier.equals(version.classifier) : version.classifier != null) {
257             return false;
258         }
259 
260         return true;
261     }
262 
263     @Override
264     public int hashCode() {
265         int result;
266         result = major;
267         result = 31 * result + minor;
268         result = 31 * result + patch;
269         result = 31 * result + (classifier != null ? classifier.hashCode() : 0);
270         return result;
271     }
272 }