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.StringUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 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 */ 031public class Version implements Comparable<Version> { 032 033 /** 034 * Static creator. 035 * 036 * @param value 037 * A string of the form <js>"#.#..."</js> where there can be any number of parts. 038 * <br>Valid values: 039 * <ul> 040 * <li><js>"1.2"</js> 041 * <li><js>"1.2.3"</js> 042 * <li><js>"0.1"</js> 043 * <li><js>".1"</js> 044 * </ul> 045 * <br>Can be <jk>null</jk>. 046 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 047 */ 048 public static Version of(String value) { 049 if (value == null) 050 return null; 051 return new Version(value); 052 } 053 054 private int[] parts; 055 056 /** 057 * Constructor 058 * 059 * @param value 060 * A string of the form <js>"#.#..."</js> where there can be any number of parts. 061 * <br>Valid values: 062 * <ul> 063 * <li><js>"1.2"</js> 064 * <li><js>"1.2.3"</js> 065 * <li><js>"0.1"</js> 066 * <li><js>".1"</js> 067 * </ul> 068 * Any parts that are not numeric are interpreted as {@link Integer#MAX_VALUE} 069 */ 070 public Version(String value) { 071 if (isEmpty(value)) 072 value = "0"; 073 String[] sParts = splita(value, '.'); 074 parts = new int[sParts.length]; 075 for (var i = 0; i < sParts.length; i++) { 076 try { 077 parts[i] = sParts[i].isEmpty() ? 0 : Integer.parseInt(sParts[i]); 078 } catch (@SuppressWarnings("unused") NumberFormatException e) { 079 parts[i] = Integer.MAX_VALUE; 080 } 081 } 082 } 083 084 @Override 085 public int compareTo(Version v) { 086 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 087 var c = parts[i] - v.parts[i]; 088 if (c != 0) 089 return c; 090 } 091 return parts.length - v.parts.length; 092 } 093 094 @Override /* Overridden from Object */ 095 public boolean equals(Object o) { 096 return o instanceof Version o2 && eq(this, o2, (x, y) -> x.equals(y)); 097 } 098 099 /** 100 * Returns <jk>true</jk> if the specified version is equal to this version. 101 * 102 * <h5 class='section'>Example:</h5> 103 * <p class='bjava'> 104 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 105 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2"</js>))); 106 * </p> 107 * 108 * @param v The version to compare to. 109 * @return <jk>true</jk> if the specified version is equal to this version. 110 */ 111 public boolean equals(Version v) { 112 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) 113 if (v.parts[i] - parts[i] != 0) 114 return false; 115 return true; 116 } 117 118 /** 119 * Returns the maintenance version part (i.e. part at index 2). 120 * 121 * @return The version part, never <jk>null</jk>. 122 */ 123 public Optional<Integer> getMaintenance() { return getPart(2); } 124 125 /** 126 * Returns the major version part (i.e. part at index 0). 127 * 128 * @return The version part, never <jk>null</jk>. 129 */ 130 public Optional<Integer> getMajor() { return getPart(0); } 131 132 /** 133 * Returns the minor version part (i.e. part at index 1). 134 * 135 * @return The version part, never <jk>null</jk>. 136 */ 137 public Optional<Integer> getMinor() { return getPart(1); } 138 139 /** 140 * Returns the version part at the specified zero-indexed value. 141 * 142 * @param index The index of the version part. 143 * @return The version part, never <jk>null</jk>. 144 */ 145 public Optional<Integer> getPart(int index) { 146 if (index < 0 || parts.length <= index) 147 return opte(); 148 return opt(parts[index]); 149 } 150 151 @Override /* Overridden from Object */ 152 public int hashCode() { 153 return Arrays.hashCode(parts); 154 } 155 156 /** 157 * Returns <jk>true</jk> if the specified version is at least this version. 158 * 159 * <h5 class='section'>Example:</h5> 160 * <p class='bjava'> 161 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1"</js>))); 162 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"2"</js>))); 163 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 164 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.0"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>))); 165 * </p> 166 * 167 * @param v The version to compare to. 168 * @return <jk>true</jk> if the specified version is at least this version. 169 */ 170 public boolean isAtLeast(Version v) { 171 return isAtLeast(v, false); 172 } 173 174 /** 175 * Returns <jk>true</jk> if the specified version is at least this version. 176 * 177 * <h5 class='section'>Example:</h5> 178 * <p class='bjava'> 179 * <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>))); 180 * <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>))); 181 * </p> 182 * 183 * @param v The version to compare to. 184 * @param exclusive Match down-to-version but not including. 185 * @return <jk>true</jk> if the specified version is at least this version. 186 */ 187 public boolean isAtLeast(Version v, boolean exclusive) { 188 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 189 var c = v.parts[i] - parts[i]; 190 if (c > 0) 191 return false; 192 else if (c < 0) 193 return true; 194 } 195 for (var i = parts.length; i < v.parts.length; i++) 196 if (v.parts[i] != 0) 197 return false; 198 // If this version has more parts than v, check if any extra parts are non-zero 199 // If they are, then this > v, so return true (this is greater than v) 200 for (var i = v.parts.length; i < parts.length; i++) 201 if (parts[i] > 0) 202 return true; // this > v, so always return true 203 return ! exclusive; 204 } 205 206 /** 207 * Returns <jk>true</jk> if the specified version is at most this version. 208 * 209 * <h5 class='section'>Example:</h5> 210 * <p class='bjava'> 211 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1"</js>))); 212 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"2"</js>))); 213 * <jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2"</js>))); 214 * <jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.0"</js>))); 215 * </p> 216 * 217 * @param v The version to compare to. 218 * @return <jk>true</jk> if the specified version is at most this version. 219 */ 220 public boolean isAtMost(Version v) { 221 return isAtMost(v, false); 222 } 223 224 /** 225 * Returns <jk>true</jk> if the specified version is at most this version. 226 * 227 * <h5 class='section'>Example:</h5> 228 * <p class='bjava'> 229 * <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>))); 230 * <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>))); 231 * </p> 232 * 233 * @param v The version to compare to. 234 * @param exclusive Match up-to-version but not including. 235 * @return <jk>true</jk> if the specified version is at most this version. 236 */ 237 public boolean isAtMost(Version v, boolean exclusive) { 238 for (int i = 0; i < Math.min(parts.length, v.parts.length); i++) { 239 var c = parts[i] - v.parts[i]; 240 if (c > 0) 241 return false; 242 else if (c < 0) 243 return true; 244 } 245 for (var i = parts.length; i < v.parts.length; i++) 246 if (v.parts[i] > 0) 247 return true; 248 return ! exclusive; 249 } 250 251 @Override /* Overridden from Object */ 252 public String toString() { 253 return join(parts, '.'); 254 } 255}