001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau;
018
019import java.util.*;
020
021import org.apache.juneau.common.utils.*;
022
023/**
024 * Represents a version string such as <js>"1.2"</js> or <js>"1.2.3"</js>
025 *
026 * <p>
027 * Used to compare version numbers.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 * </ul>
031 */
032public class Version implements Comparable<Version> {
033
034   private int[] parts;
035
036   /**
037    * Static creator.
038    *
039    * @param value
040    *    A string of the form <js>"#.#..."</js> where there can be any number of parts.
041    *    <br>Valid values:
042    *    <ul>
043    *       <li><js>"1.2"</js>
044    *       <li><js>"1.2.3"</js>
045    *       <li><js>"0.1"</js>
046    *       <li><js>".1"</js>
047    *    </ul>
048    *    <br>Can be <jk>null</jk>.
049    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
050    */
051   public static Version of(String value) {
052      if (value == null)
053         return null;
054      return new Version(value);
055   }
056
057
058   /**
059    * Constructor
060    *
061    * @param value
062    *    A string of the form <js>"#.#..."</js> where there can be any number of parts.
063    *    <br>Valid values:
064    *    <ul>
065    *       <li><js>"1.2"</js>
066    *       <li><js>"1.2.3"</js>
067    *       <li><js>"0.1"</js>
068    *       <li><js>".1"</js>
069    *    </ul>
070    *    Any parts that are not numeric are interpreted as {@link Integer#MAX_VALUE}
071    */
072   public Version(String value) {
073      if (Utils.isEmpty(value))
074         value = "0";
075      String[] sParts = Utils.splita(value, '.');
076      parts = new int[sParts.length];
077      for (int i = 0; i < sParts.length; i++) {
078         try {
079            parts[i] = sParts[i].isEmpty() ? 0 : Integer.parseInt(sParts[i]);
080         } catch (NumberFormatException e) {
081            parts[i] = Integer.MAX_VALUE;
082         }
083      }
084   }
085
086   /**
087    * Returns the version part at the specified zero-indexed value.
088    *
089    * @param index The index of the version part.
090    * @return The version part, never <jk>null</jk>.
091    */
092   public Optional<Integer> getPart(int index) {
093      if (index < 0 || parts.length <= index)
094         return Utils.opte();
095      return Utils.opt(parts[index]);
096   }
097
098   /**
099    * Returns the major version part (i.e. part at index 0).
100    *
101    * @return The version part, never <jk>null</jk>.
102    */
103   public Optional<Integer> getMajor() {
104      return getPart(0);
105   }
106
107   /**
108    * Returns the minor version part (i.e. part at index 1).
109    *
110    * @return The version part, never <jk>null</jk>.
111    */
112   public Optional<Integer> getMinor() {
113      return getPart(1);
114   }
115
116   /**
117    * Returns the maintenance version part (i.e. part at index 2).
118    *
119    * @return The version part, never <jk>null</jk>.
120    */
121   public Optional<Integer> getMaintenance() {
122      return getPart(2);
123   }
124
125   /**
126    * Returns <jk>true</jk> if the specified version is at least this version.
127    *
128    * <h5 class='section'>Example:</h5>
129    * <p class='bjava'>
130    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1"</js>)));
131    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"2"</js>)));
132    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
133    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.0"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
134    * </p>
135    *
136    * @param v The version to compare to.
137    * @return <jk>true</jk> if the specified version is at least this version.
138    */
139   public boolean isAtLeast(Version v) {
140      return isAtLeast(v, false);
141   }
142
143
144   /**
145    * Returns <jk>true</jk> if the specified version is at least this version.
146    *
147    * <h5 class='section'>Example:</h5>
148    * <p class='bjava'>
149    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>false</jk>)));
150    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>true</jk>)));
151    * </p>
152    *
153    * @param v The version to compare to.
154    * @param exclusive Match down-to-version but not including.
155    * @return <jk>true</jk> if the specified version is at least this version.
156    */
157   public boolean isAtLeast(Version v, boolean exclusive) {
158      for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) {
159         int c = v.parts[i] - parts[i];
160         if (c > 0)
161            return false;
162         else if (c < 0)
163            return true;
164      }
165      for (int i = parts.length; i < v.parts.length; i++)
166         if (v.parts[i] != 0)
167            return false;
168      return ! exclusive;
169   }
170
171   /**
172    * Returns <jk>true</jk> if the specified version is at most this version.
173    *
174    * <h5 class='section'>Example:</h5>
175    * <p class='bjava'>
176    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1"</js>)));
177    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"2"</js>)));
178    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2"</js>)));
179    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.0"</js>)));
180    * </p>
181    *
182    * @param v The version to compare to.
183    * @return <jk>true</jk> if the specified version is at most this version.
184    */
185   public boolean isAtMost(Version v) {
186      return isAtMost(v, false);
187   }
188
189   /**
190    * Returns <jk>true</jk> if the specified version is at most this version.
191    *
192    * <h5 class='section'>Example:</h5>
193    * <p class='bjava'>
194    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>false</jk>)));
195    *    <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>true</jk>)));
196    * </p>
197    *
198    * @param v The version to compare to.
199    * @param exclusive Match up-to-version but not including.
200    * @return <jk>true</jk> if the specified version is at most this version.
201    */
202   public boolean isAtMost(Version v, boolean exclusive) {
203      for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) {
204         int c = parts[i] - v.parts[i];
205         if (c > 0)
206            return false;
207         else if (c < 0)
208            return true;
209      }
210      for (int i = parts.length; i < v.parts.length; i++)
211         if (v.parts[i] > 0)
212            return true;
213      return ! exclusive;
214   }
215
216   /**
217    * Returns <jk>true</jk> if the specified version is equal to this version.
218    *
219    * <h5 class='section'>Example:</h5>
220    * <p class='bjava'>
221    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
222    *    <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2"</js>)));
223    * </p>
224    *
225    * @param v The version to compare to.
226    * @return <jk>true</jk> if the specified version is equal to this version.
227    */
228   public boolean equals(Version v) {
229      for (int i = 0; i < Math.min(parts.length, v.parts.length); i++)
230         if (v.parts[i] - parts[i] != 0)
231            return false;
232      return true;
233   }
234
235   @Override /* Object */
236   public boolean equals(Object o) {
237      return o instanceof Version && equals((Version)o);
238   }
239
240   @Override /* Object */
241   public String toString() {
242      return Utils.join(parts, '.');
243   }
244
245   @Override
246   public int compareTo(Version v) {
247      for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) {
248         int c = parts[i] - v.parts[i];
249         if (c != 0)
250            return c;
251      }
252      return parts.length - v.parts.length;
253   }
254}