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.commons.lang; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.util.*; 023 024/** 025 * Represents a version string such as <js>"1.2"</js> or <js>"1.2.3"</js> 026 * 027 * <p> 028 * Used to compare version numbers. 029 * 030 * <h5 class='section'>Equality vs. matching:</h5> 031 * <p> 032 * {@link #equals(Object)}, {@link #hashCode()} and {@link #compareTo(Version)} are <b>strict</b> and 033 * mutually consistent — two versions are only equal when their full part sequences are identical 034 * (e.g. <js>"1.2"</js> is <b>not</b> equal to <js>"1.2.3"</js> or <js>"1.2.0"</js>). For the looser 035 * prefix/compatibility comparison (where trailing parts are ignored), use {@link #matches(Version)}. 036 */ 037public class Version implements Comparable<Version> { 038 039 /** 040 * Static creator. 041 * 042 * @param value 043 * A string of the form <js>"#.#..."</js> where there can be any number of parts. 044 * <br>Valid values: 045 * <ul> 046 * <li><js>"1.2"</js> 047 * <li><js>"1.2.3"</js> 048 * <li><js>"0.1"</js> 049 * <li><js>".1"</js> 050 * </ul> 051 * <br>Can be <jk>null</jk>. 052 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 053 */ 054 public static Version of(String value) { 055 if (value == null) 056 return null; 057 return new Version(value); 058 } 059 060 private final int[] parts; 061 062 /** 063 * Constructor 064 * 065 * @param value 066 * A string of the form <js>"#.#..."</js> where there can be any number of parts. 067 * <br>Valid values: 068 * <ul> 069 * <li><js>"1.2"</js> 070 * <li><js>"1.2.3"</js> 071 * <li><js>"0.1"</js> 072 * <li><js>".1"</js> 073 * </ul> 074 * Any parts that are not numeric are interpreted as {@link Integer#MAX_VALUE} 075 * <br>Can be <jk>null</jk> or empty (treated as version <js>"0"</js>). 076 */ 077 public Version(String value) { 078 if (isEmpty(value)) 079 value = "0"; 080 String[] sParts = splita(value, '.'); 081 parts = new int[sParts.length]; 082 for (var i = 0; i < sParts.length; i++) { 083 try { 084 parts[i] = sParts[i].isEmpty() ? 0 : Integer.parseInt(sParts[i]); 085 } catch (@SuppressWarnings("unused") NumberFormatException e) { 086 parts[i] = Integer.MAX_VALUE; 087 } 088 } 089 } 090 091 @Override 092 public int compareTo(Version v) { 093 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 094 var c = parts[i] - v.parts[i]; 095 if (c != 0) 096 return c; 097 } 098 return parts.length - v.parts.length; 099 } 100 101 @Override /* Overridden from Object */ 102 public boolean equals(Object o) { 103 return o instanceof Version o2 && Arrays.equals(parts, o2.parts); 104 } 105 106 /** 107 * Returns the maintenance version part (i.e. part at index 2). 108 * 109 * @return The version part, never <jk>null</jk>. 110 */ 111 public Optional<Integer> getMaintenance() { return getPart(2); } 112 113 /** 114 * Returns the major version part (i.e. part at index 0). 115 * 116 * @return The version part, never <jk>null</jk>. 117 */ 118 public Optional<Integer> getMajor() { return getPart(0); } 119 120 /** 121 * Returns the minor version part (i.e. part at index 1). 122 * 123 * @return The version part, never <jk>null</jk>. 124 */ 125 public Optional<Integer> getMinor() { return getPart(1); } 126 127 /** 128 * Returns the version part at the specified zero-indexed value. 129 * 130 * @param index The index of the version part. 131 * @return The version part, never <jk>null</jk>. 132 */ 133 public Optional<Integer> getPart(int index) { 134 if (index < 0 || parts.length <= index) 135 return oe(); 136 return o(parts[index]); 137 } 138 139 @Override /* Overridden from Object */ 140 public int hashCode() { 141 return Arrays.hashCode(parts); 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"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1"</js>))); 150 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"2"</js>))); 151 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 152 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.0"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 153 * </p> 154 * 155 * @param v The version to compare to. Must not be <jk>null</jk>. 156 * @return <jk>true</jk> if the specified version is at least this version. 157 */ 158 public boolean isAtLeast(Version v) { 159 return isAtLeast(v, false); 160 } 161 162 /** 163 * Returns <jk>true</jk> if the specified version is at least this version. 164 * 165 * <h5 class='section'>Example:</h5> 166 * <p class='bjava'> 167 * <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>))); 168 * <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>))); 169 * </p> 170 * 171 * @param v The version to compare to. Must not be <jk>null</jk>. 172 * @param exclusive Match down-to-version but not including. 173 * @return <jk>true</jk> if the specified version is at least this version. 174 */ 175 public boolean isAtLeast(Version v, boolean exclusive) { 176 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 177 var c = v.parts[i] - parts[i]; 178 if (c > 0) 179 return false; 180 else if (c < 0) 181 return true; 182 } 183 for (var i = parts.length; i < v.parts.length; i++) 184 if (v.parts[i] != 0) 185 return false; 186 // If this version has more parts than v, check if any extra parts are non-zero 187 // If they are, then this > v, so return true (this is greater than v) 188 for (var i = v.parts.length; i < parts.length; i++) 189 if (parts[i] > 0) 190 return true; // this > v, so always return true 191 return ! exclusive; 192 } 193 194 /** 195 * Returns <jk>true</jk> if the specified version is at most this version. 196 * 197 * <h5 class='section'>Example:</h5> 198 * <p class='bjava'> 199 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1"</js>))); 200 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"2"</js>))); 201 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2"</js>))); 202 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.0"</js>))); 203 * </p> 204 * 205 * @param v The version to compare to. Must not be <jk>null</jk>. 206 * @return <jk>true</jk> if the specified version is at most this version. 207 */ 208 public boolean isAtMost(Version v) { 209 return isAtMost(v, false); 210 } 211 212 /** 213 * Returns <jk>true</jk> if the specified version is at most this version. 214 * 215 * <h5 class='section'>Example:</h5> 216 * <p class='bjava'> 217 * <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>))); 218 * <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>))); 219 * </p> 220 * 221 * @param v The version to compare to. Must not be <jk>null</jk>. 222 * @param exclusive Match up-to-version but not including. 223 * @return <jk>true</jk> if the specified version is at most this version. 224 */ 225 public boolean isAtMost(Version v, boolean exclusive) { 226 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 227 var c = parts[i] - v.parts[i]; 228 if (c > 0) 229 return false; 230 else if (c < 0) 231 return true; 232 } 233 for (var i = parts.length; i < v.parts.length; i++) 234 if (v.parts[i] > 0) 235 return true; 236 return ! exclusive; 237 } 238 239 /** 240 * Returns <jk>true</jk> if this version matches the specified version, comparing only the parts they have in 241 * common (i.e. one version is a prefix of the other). 242 * 243 * <p> 244 * This is a looser comparison than {@link #equals(Object)}, which requires the full version part sequences to be 245 * identical. Two versions <i>match</i> when every part up to the length of the shorter version is identical; any 246 * trailing parts on the longer version are ignored. Unlike {@link #equals(Object)}, this relation is <b>not</b> 247 * consistent with {@link #hashCode()} and must never be used as the basis for hash- or tree-based collection 248 * membership. 249 * 250 * <h5 class='section'>Example:</h5> 251 * <p class='bjava'> 252 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).matches(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 253 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).matches(Version.<jsm>of</jsm>(<js>"1.2"</js>))); 254 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).matches(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 255 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).matches(Version.<jsm>of</jsm>(<js>"1.2.4"</js>))); 256 * </p> 257 * 258 * @param v The version to compare to. Must not be <jk>null</jk>. 259 * @return <jk>true</jk> if this version matches the specified version on their common parts. 260 */ 261 public boolean matches(Version v) { 262 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) 263 if (v.parts[i] - parts[i] != 0) 264 return false; 265 return true; 266 } 267 268 @Override /* Overridden from Object */ 269 public String toString() { 270 return join(parts, '.'); 271 } 272}