001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.utils; 021 022import com.google.common.base.Splitter; 023import java.util.List; 024import javax.annotation.concurrent.Immutable; 025 026import static java.lang.Integer.parseInt; 027import static java.util.Objects.requireNonNull; 028import static org.apache.commons.lang.StringUtils.substringAfter; 029import static org.apache.commons.lang.StringUtils.substringBefore; 030import static org.apache.commons.lang.StringUtils.trimToEmpty; 031 032/** 033 * Version composed of 3 integer-sequences (major, minor and patch fields) and optionally a qualifier. 034 * <p> 035 * Examples: 1.0, 1.0.0, 1.2.3, 1.2-beta1, 1.2.1-beta-1 036 * <p> 037 * <h3>IMPORTANT NOTE</h3> 038 * Qualifier is ignored when comparing objects (methods {@link #equals(Object)}, {@link #hashCode()} 039 * and {@link #compareTo(Version)}). 040 * <p> 041 * <pre> 042 * assertThat(Version.parse("1.2")).isEqualTo(Version.parse("1.2-beta1")); 043 * assertThat(Version.parse("1.2").compareTo(Version.parse("1.2-beta1"))).isZero(); 044 * </pre> 045 * 046 * @since 5.5 047 */ 048@Immutable 049public class Version implements Comparable<Version> { 050 051 private static final String QUALIFIER_SEPARATOR = "-"; 052 private static final char SEQUENCE_SEPARATOR = '.'; 053 private static final Splitter SEQUENCE_SPLITTER = Splitter.on(SEQUENCE_SEPARATOR); 054 055 private final int major; 056 private final int minor; 057 private final int patch; 058 private final String qualifier; 059 060 private Version(int major, int minor, int patch, String qualifier) { 061 requireNonNull(qualifier, "Version qualifier must not be null"); 062 this.major = major; 063 this.minor = minor; 064 this.patch = patch; 065 this.qualifier = qualifier; 066 } 067 068 public int major() { 069 return major; 070 } 071 072 public int minor() { 073 return minor; 074 } 075 076 public int patch() { 077 return patch; 078 } 079 080 /** 081 * @return non-null suffix. Empty if absent, else the suffix without the first character "-" 082 */ 083 public String qualifier() { 084 return qualifier; 085 } 086 087 /** 088 * Convert a {@link String} to a Version. Supported formats: 089 * <ul> 090 * <li>1</li> 091 * <li>1.2</li> 092 * <li>1.2.3</li> 093 * <li>1-beta-1</li> 094 * <li>1.2-beta-1</li> 095 * <li>1.2.3-beta-1</li> 096 * </ul> 097 * Note that the optional qualifier is the part after the first "-". 098 * 099 * @throws IllegalArgumentException if parameter is badly formatted, for example 100 * if it defines 4 integer-sequences. 101 */ 102 public static Version parse(String text) { 103 String s = trimToEmpty(text); 104 String qualifier = substringAfter(s, QUALIFIER_SEPARATOR); 105 if (!qualifier.isEmpty()) { 106 s = substringBefore(s, QUALIFIER_SEPARATOR); 107 } 108 List<String> split = SEQUENCE_SPLITTER.splitToList(s); 109 int major = 0; 110 int minor = 0; 111 int patch = 0; 112 int size = split.size(); 113 if (size > 0) { 114 major = parseSequence(split.get(0)); 115 if (size > 1) { 116 minor = parseSequence(split.get(1)); 117 if (size > 2) { 118 patch = parseSequence(split.get(2)); 119 if (size > 3) { 120 throw new IllegalArgumentException("Only 3 sequences are accepted"); 121 } 122 } 123 } 124 } 125 return new Version(major, minor, patch, qualifier); 126 } 127 128 public static Version create(int major, int minor) { 129 return new Version(major, minor, 0, ""); 130 } 131 132 public static Version create(int major, int minor, int patch) { 133 return new Version(major, minor, patch, ""); 134 } 135 136 public static Version create(int major, int minor, int patch, String qualifier) { 137 return new Version(major, minor, patch, qualifier); 138 } 139 140 private static int parseSequence(String sequence) { 141 if (sequence.isEmpty()) { 142 return 0; 143 } 144 return parseInt(sequence); 145 } 146 147 public boolean isGreaterThanOrEqual(Version than) { 148 return this.compareTo(than) >= 0; 149 } 150 151 @Override 152 public boolean equals(Object o) { 153 if (this == o) { 154 return true; 155 } 156 if (!(o instanceof Version)) { 157 return false; 158 } 159 Version other = (Version) o; 160 return major == other.major && minor == other.minor && patch == other.patch; 161 } 162 163 @Override 164 public int hashCode() { 165 int result = major; 166 result = 31 * result + minor; 167 result = 31 * result + patch; 168 return result; 169 } 170 171 @Override 172 public int compareTo(Version other) { 173 int c = major - other.major; 174 if (c == 0) { 175 c = minor - other.minor; 176 if (c == 0) { 177 c = patch - other.patch; 178 } 179 } 180 return c; 181 } 182 183 @Override 184 public String toString() { 185 StringBuilder sb = new StringBuilder(); 186 sb.append(major).append(SEQUENCE_SEPARATOR).append(minor); 187 if (patch > 0) { 188 sb.append(SEQUENCE_SEPARATOR).append(patch); 189 } 190 if (!qualifier.isEmpty()) { 191 sb.append(QUALIFIER_SEPARATOR).append(qualifier); 192 } 193 return sb.toString(); 194 } 195}