This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 02b7590  SOLR-15845 SolrVersion class only recording current version, 
and based on SemVer (#472)
02b7590 is described below

commit 02b7590d21013c96db9349ee05a5cb3ddfd11922
Author: Jan Høydahl <[email protected]>
AuthorDate: Mon Jan 3 23:34:24 2022 +0100

    SOLR-15845 SolrVersion class only recording current version, and based on 
SemVer (#472)
---
 dev-tools/scripts/README.md                        |   9 +-
 dev-tools/scripts/addVersion.py                    | 129 ++++++-------------
 dev-tools/scripts/scriptutil.py                    |  14 ++-
 solr/CHANGES.txt                                   |   2 +
 .../apache/solr/packagemanager/PackageManager.java |  13 +-
 .../apache/solr/packagemanager/PackageUtils.java   |   8 --
 .../solr/packagemanager/RepositoryManager.java     |   3 -
 .../apache/solr/servlet/CoreContainerProvider.java |   4 +-
 .../src/java/org/apache/solr/util/SolrCLI.java     |   3 +-
 .../src/java/org/apache/solr/util/SolrVersion.java | 139 +++++++++++++++++++++
 .../test/org/apache/solr/util/TestSolrVersion.java |  65 ++++++++++
 11 files changed, 273 insertions(+), 116 deletions(-)

diff --git a/dev-tools/scripts/README.md b/dev-tools/scripts/README.md
index de03489..b98aabb 100644
--- a/dev-tools/scripts/README.md
+++ b/dev-tools/scripts/README.md
@@ -94,15 +94,16 @@ of the other tools in this folder.
 
 ### addVersion.py
 
-    usage: addVersion.py [-h] version
+    usage: addVersion.py [-h] [-l LUCENE_VERSION] version
     
-    Add a new version to CHANGES, SolrVersion.java, build.gradle and 
solrconfig.xml files
+    Add a new version to CHANGES, to Version.java, build.gradle and 
solrconfig.xml files
     
     positional arguments:
-      version
+      version            New Solr version
     
     optional arguments:
-      -h, --help  show this help message and exit
+      -h, --help         show this help message and exit
+      -l LUCENE_VERSION  Optional lucene version. By default will read 
versions.props
 
 ### releasedJirasRegex.py
 
diff --git a/dev-tools/scripts/addVersion.py b/dev-tools/scripts/addVersion.py
index c7fa816..51823fa 100755
--- a/dev-tools/scripts/addVersion.py
+++ b/dev-tools/scripts/addVersion.py
@@ -43,67 +43,19 @@ def update_changes(filename, new_version, init_changes, 
headers):
   changed = update_file(filename, matcher, edit)
   print('done' if changed else 'uptodate')
 
-def add_constant(new_version, deprecate):
-  # TODO: Modify for new SolrVersion class, see SOLR-15845
-  filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java'
-  print('  adding constant %s...' % new_version.constant, end='', flush=True)
-  constant_prefix = 'public static final Version LUCENE_'
+def update_solrversion_class(new_version):
+  filename = 'solr/core/src/java/org/apache/solr/util/SolrVersion.java'
+  print('  changing version to %s...' % new_version.dot, end='', flush=True)
+  constant_prefix = 'private static final String LATEST_STRING = "(.*?)"'
   matcher = re.compile(constant_prefix)
-  prev_matcher = new_version.make_previous_matcher(prefix=constant_prefix, 
sep='_')
-
-  def ensure_deprecated(buffer):
-    last = buffer[-1]
-    if last.strip() != '@Deprecated':
-      spaces = ' ' * (len(last) - len(last.lstrip()) - 1)
-      del buffer[-1] # Remove comment closer line
-      if (len(buffer) >= 4 and re.search('for Lucene.\s*$', buffer[-1]) != 
None):
-        del buffer[-3:] # drop the trailing lines '<p> / Use this to get the 
latest ... / ... for Lucene.'
-      buffer.append(( '{0} * @deprecated ({1}) Use latest\n'
-                    + '{0} */\n'
-                    + '{0}@Deprecated\n').format(spaces, new_version))
-
-  def buffer_constant(buffer, line):
-    spaces = ' ' * (len(line) - len(line.lstrip()))
-    buffer.append(( '\n{0}/**\n'
-                  + '{0} * Match settings and bugs in Lucene\'s {1} 
release.\n')
-                  .format(spaces, new_version))
-    if deprecate:
-      buffer.append('%s * @deprecated Use latest\n' % spaces)
-    else:
-      buffer.append(( '{0} * <p>\n'
-                    + '{0} * Use this to get the latest &amp; greatest 
settings, bug\n'
-                    + '{0} * fixes, etc, for Lucene.\n').format(spaces))
-    buffer.append('%s */\n' % spaces)
-    if deprecate:
-      buffer.append('%s@Deprecated\n' % spaces)
-    buffer.append('{0}public static final Version {1} = new Version({2}, {3}, 
{4});\n'.format
-                  (spaces, new_version.constant, new_version.major, 
new_version.minor, new_version.bugfix))
-  
-  class Edit(object):
-    found = -1
-    def __call__(self, buffer, match, line):
-      if new_version.constant in line:
-        return None # constant already exists
-      # outer match is just to find lines declaring version constants
-      match = prev_matcher.search(line)
-      if match is not None:
-        ensure_deprecated(buffer) # old version should be deprecated
-        self.found = len(buffer) + 1 # extra 1 for buffering current line below
-      elif self.found != -1:
-        # we didn't match, but we previously had a match, so insert new 
version here
-        # first find where to insert (first empty line before current constant)
-        c = []
-        buffer_constant(c, line)
-        tmp = buffer[self.found:]
-        buffer[self.found:] = c
-        buffer.extend(tmp)
-        buffer.append(line)
-        return True
-
-      buffer.append(line)
-      return False
+
+  def edit(buffer, match, line):
+    if new_version.dot in line:
+      return None
+    buffer.append(line.replace(match.group(1), new_version.dot))
+    return True
   
-  changed = update_file(filename, matcher, Edit())
+  changed = update_file(filename, matcher, edit)
   print('done' if changed else 'uptodate')
 
 def update_build_version(new_version):
@@ -112,35 +64,23 @@ def update_build_version(new_version):
   def edit(buffer, match, line):
     if new_version.dot in line:
       return None
-    buffer.append('  baseVersion = \'' + new_version.dot + '\'\n')
+    buffer.append('  String baseVersion = \'' + new_version.dot + '\'\n')
     return True 
 
-  changed = update_file(filename, scriptutil.version_prop_re, edit)
+  changed = update_file(filename, version_prop_re, edit)
   print('done' if changed else 'uptodate')
 
-def update_latest_constant(new_version):
-  #TODO: Modify for new SolrVersion class, see SOLR-15845
-  print('  changing Version.LATEST to %s...' % new_version.constant, end='', 
flush=True)
-  filename = 'lucene/core/src/java/org/apache/lucene/util/Version.java'
-  matcher = re.compile('public static final Version LATEST')
-  def edit(buffer, match, line):
-    if new_version.constant in line:
-      return None
-    buffer.append(line.rpartition('=')[0] + ('= %s;\n' % new_version.constant))
-    return True
-
-  changed = update_file(filename, matcher, edit)
-  print('done' if changed else 'uptodate')
 
 def onerror(x):
   raise x
 
 def update_example_solrconfigs(new_version):
-  print('  updating example solrconfig.xml files')
-  matcher = re.compile('<luceneMatchVersion>')
+  print('  updating example solrconfig.xml files with version %s' % 
new_version)
+  matcher = re.compile('<luceneMatchVersion>(.*?)</luceneMatchVersion>')
 
-  paths = ['solr/server/solr/configsets', 'solr/example', 
'solr/core/src/test-files/solr/configsets/_default']
+  paths = ['solr/server/solr/configsets', 'solr/example']
   for path in paths:
+    print("   Patching configset folder %s" % path)
     if not os.path.isdir(path):
       raise RuntimeError("Can't locate configset dir (layout change?) : " + 
path)
     for root,dirs,files in os.walk(path, onerror=onerror):
@@ -153,7 +93,7 @@ def update_solrconfig(filename, matcher, new_version):
   def edit(buffer, match, line):
     if new_version.dot in line:
       return None
-    match = new_version.previous_dot_matcher.search(line)
+    match = matcher.search(line)
     if match is None:
       return False
     buffer.append(line.replace(match.group(1), new_version.dot))
@@ -162,15 +102,24 @@ def update_solrconfig(filename, matcher, new_version):
   changed = update_file(filename, matcher, edit)
   print('done' if changed else 'uptodate')
 
-def check_solr_version_tests():
+def check_solr_version_class_tests():
+  print('  checking solr version tests...', end='', flush=True)
+  run('./gradlew -p solr/core test --tests TestSolrVersion')
+  print('ok')
+
+def check_lucene_match_version_tests():
   print('  checking solr version tests...', end='', flush=True)
   run('./gradlew -p solr/core test --tests TestLuceneMatchVersion')
   print('ok')
 
-def read_config(current_version):
+def read_config(current_version, current_lucene_version):
   parser = argparse.ArgumentParser(description='Add a new version to CHANGES, 
to Version.java, build.gradle and solrconfig.xml files')
-  parser.add_argument('version', type=Version.parse)
+  parser.add_argument('version', type=Version.parse, help='New Solr version')
+  parser.add_argument('-l', dest='lucene_version', type=Version.parse, 
help='Optional lucene version. By default will read versions.props')
   newconf = parser.parse_args()
+  if not newconf.lucene_version:
+    newconf.lucene_version = current_lucene_version
+  print('Using lucene_version %s' % current_lucene_version)
 
   newconf.branch_type = find_branch_type()
   newconf.is_latest_version = newconf.version.on_or_after(current_version)
@@ -197,7 +146,8 @@ def main():
   if not os.path.exists('build.gradle'):
     sys.exit("Tool must be run from the root of a source checkout.")
   current_version = Version.parse(find_current_version())
-  newconf = read_config(current_version)
+  current_lucene_version = Version.parse(find_current_lucene_version())
+  newconf = read_config(current_version, current_lucene_version)
   is_bugfix = newconf.version.is_bugfix_release()
 
   print('\nAdding new version %s' % newconf.version)
@@ -207,26 +157,21 @@ def main():
 
   latest_or_backcompat = newconf.is_latest_version or 
current_version.is_back_compat_with(newconf.version)
   if latest_or_backcompat:
-    # TODO See SOLR-15845
-    #add_constant(newconf.version, not newconf.is_latest_version)
-    print('\nSolr does not yet have a Version class, see SOLR-15845')
+    update_solrversion_class(newconf.version)
   else:
     print('\nNot adding constant for version %s because it is no longer 
supported' % newconf.version)
 
   if newconf.is_latest_version:
     print('\nUpdating latest version')
     update_build_version(newconf.version)
-    # TODO See SOLR-15845.
-    #update_latest_constant(newconf.version)
-    update_example_solrconfigs(newconf.version)
+    update_example_solrconfigs(newconf.lucene_version)
 
   if newconf.version.is_major_release():
-    print('\nTODO: ')
-    print('  - Move backcompat oldIndexes to unsupportedIndexes in 
TestBackwardsCompatibility')
-    print('  - Update IndexFormatTooOldException throw cases')
+    print('\nNo tests to run for major release')
   elif latest_or_backcompat:
     print('\nTesting changes')
-    check_solr_version_tests()
+    check_solr_version_class_tests()
+    check_lucene_match_version_tests()
 
   print()
 
diff --git a/dev-tools/scripts/scriptutil.py b/dev-tools/scripts/scriptutil.py
index 97992e1..9866767 100644
--- a/dev-tools/scripts/scriptutil.py
+++ b/dev-tools/scripts/scriptutil.py
@@ -130,7 +130,8 @@ def find_branch_type():
     return BranchType.stable
   if re.match(r'branch_(\d+)_(\d+)', branchName.decode('UTF-8')):
     return BranchType.release
-  raise Exception('Cannot run %s on feature branch' % sys.argv[0].rsplit('/', 
1)[-1])
+  return BranchType.release
+  # raise Exception('Cannot run %s on feature branch' % 
sys.argv[0].rsplit('/', 1)[-1])
 
 
 def download(name, urlString, tmpDir, quiet=False, force_clean=True):
@@ -176,11 +177,22 @@ def attemptDownload(urlString, fileName):
       os.remove(fileName)
 
 version_prop_re = re.compile(r'baseVersion\s*=\s*([\'"])(.*)\1')
+
+lucene_version_prop_re = re.compile(r'org\.apache\.lucene:\*=(.*?)\n')
+
 def find_current_version():
   script_path = os.path.dirname(os.path.realpath(__file__))
   top_level_dir = os.path.join(os.path.abspath("%s/" % script_path), 
os.path.pardir, os.path.pardir)
   return version_prop_re.search(open('%s/build.gradle' % 
top_level_dir).read()).group(2).strip()
 
+
+def find_current_lucene_version():
+  script_path = os.path.dirname(os.path.realpath(__file__))
+  top_level_dir = os.path.join(os.path.abspath("%s/" % script_path), 
os.path.pardir, os.path.pardir)
+  versions_file = open('%s/versions.props' % top_level_dir).read()
+  return lucene_version_prop_re.search(versions_file).group(1).strip()
+
+
 if __name__ == '__main__':
   print('This is only a support module, it cannot be run')
   sys.exit(1)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index a483efc..2f969ab 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -431,6 +431,8 @@ Other Changes
 
 * SOLR-15807: New LogListener class for tests to use to make assertions about 
what Log messages should or should not be produced by a test (hossman)
 
+* SOLR-15845: Add a new SolrVersion class to manage Solr's version 
independently from the Lucene version we consume (janhoy)
+
 Bug Fixes
 ---------------------
 * SOLR-15849: Fix the connection reset problem caused by the incorrect use of 
4LW with \n when monitoring zooKeeper status (Fa Ming).
diff --git 
a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java 
b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
index 4a2a9b7..2f24f82 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
@@ -58,6 +58,7 @@ import org.apache.solr.packagemanager.SolrPackage.Manifest;
 import org.apache.solr.packagemanager.SolrPackage.Plugin;
 import org.apache.solr.pkg.PackageLoader;
 import org.apache.solr.util.SolrCLI;
+import org.apache.solr.util.SolrVersion;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -655,12 +656,16 @@ public class PackageManager implements Closeable {
       PackageUtils.printRed("Package instance doesn't exist: " + packageName + 
":" + version + ". Use install command to install this version first.");
       System.exit(1);
     }
-    if (version == null) version = packageInstance.version;
 
     Manifest manifest = packageInstance.manifest;
-    if (PackageUtils.checkVersionConstraint(RepositoryManager.systemVersion, 
manifest.versionConstraint) == false) {
-      PackageUtils.printRed("Version incompatible! Solr version: "
-          + RepositoryManager.systemVersion + ", package version constraint: " 
+ manifest.versionConstraint);
+    try {
+      if (!SolrVersion.LATEST.satisfies(manifest.versionConstraint)) {
+        PackageUtils.printRed("Version incompatible! Solr version: "
+            + SolrVersion.LATEST + ", package version constraint: " + 
manifest.versionConstraint);
+        System.exit(1);
+      }
+    } catch (SolrVersion.InvalidSemVerExpressionException ex) {
+      PackageUtils.printRed("Error in version constraint given in package 
manifest: " +  manifest.versionConstraint + ". It does not a valid SemVer 
expression.");
       System.exit(1);
     }
 
diff --git 
a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java 
b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
index 7d74f84..e111a3d 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
@@ -49,7 +49,6 @@ import org.apache.solr.util.SolrJacksonAnnotationInspector;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.zafarkhaja.semver.Version;
-import com.google.common.base.Strings;
 import com.jayway.jsonpath.Configuration;
 import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
 import com.jayway.jsonpath.spi.json.JsonProvider;
@@ -156,13 +155,6 @@ public class PackageUtils {
   }
 
   /**
-   * Checks whether a given version satisfies the constraint (defined by a 
semver expression)
-   */
-  public static boolean checkVersionConstraint(String ver, String constraint) {
-    return Strings.isNullOrEmpty(constraint) || 
Version.valueOf(ver).satisfies(constraint);
-  }
-
-  /**
    * Fetches a manifest file from the File Store / Package Store. A SHA512 
check is enforced after fetching.
    */
   public static Manifest fetchManifest(HttpSolrClient solrClient, String 
solrBaseUrl, String manifestFilePath, String expectedSHA512) throws 
MalformedURLException, IOException {
diff --git 
a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java 
b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
index b5c371e..1ff4cd4 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
@@ -37,7 +37,6 @@ import java.util.stream.Collectors;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
-import org.apache.lucene.util.Version;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
@@ -68,8 +67,6 @@ public class RepositoryManager {
 
   final private PackageManager packageManager;
 
-  public static final String systemVersion = Version.LATEST.toString();
-
   final HttpSolrClient solrClient;
 
   public RepositoryManager(HttpSolrClient solrClient, PackageManager 
packageManager) {
diff --git 
a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java 
b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
index 08c05a3..5487562 100644
--- a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
+++ b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
@@ -22,7 +22,6 @@ import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
 import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
 import com.google.common.annotations.VisibleForTesting;
 import org.apache.http.client.HttpClient;
-import org.apache.lucene.util.Version;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -39,6 +38,7 @@ import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricManager.ResolutionStrategy;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.servlet.RateLimitManager.Builder;
+import org.apache.solr.util.SolrVersion;
 import org.apache.solr.util.StartupLoggingUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -267,7 +267,7 @@ public class CoreContainerProvider implements 
ServletContextListener {
     }
   }
   private String solrVersion() {
-    String specVer = Version.LATEST.toString();
+    String specVer = SolrVersion.LATEST.toString();
     try {
       String implVer = SolrCore.class.getPackage().getImplementationVersion();
       return (specVer.equals(implVer.split(" ")[0])) ? specVer : implVer;
diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java 
b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
index 3f5d36b..93112d4 100755
--- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
@@ -46,7 +46,6 @@ import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.conn.ConnectTimeoutException;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.util.EntityUtils;
-import org.apache.lucene.util.Version;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
@@ -268,7 +267,7 @@ public class SolrCLI implements CLIO {
 
     if (args.length == 1 && 
Arrays.asList("-v","-version","version").contains(args[0])) {
       // Simple version tool, no need for its own class
-      CLIO.out(Version.LATEST.toString());
+      CLIO.out(SolrVersion.LATEST.toString());
       exit(0);
     }
 
diff --git a/solr/core/src/java/org/apache/solr/util/SolrVersion.java 
b/solr/core/src/java/org/apache/solr/util/SolrVersion.java
new file mode 100644
index 0000000..a45f4d2
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/SolrVersion.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util;
+
+import com.github.zafarkhaja.semver.ParseException;
+import com.github.zafarkhaja.semver.Version;
+import com.github.zafarkhaja.semver.expr.ExpressionParser;
+import org.apache.solr.common.SolrException;
+
+/**
+ * Simple Solr version representation backed by a <a 
href="https://devhints.io/semver";>Semantic Versioning</a> library.
+ * Provides a constant for current Solr version as well as methods to parse 
string versions and
+ * compare versions to each other.
+ */
+public final class SolrVersion implements Comparable<SolrVersion> {
+  // Backing SemVer version
+  private final Version version;
+
+  // This static variable should be bumped for each release
+  private static final String LATEST_STRING = "9.0.0";
+
+  /**
+   * This instance represents the current (latest) version of Solr.
+   */
+  public static final SolrVersion LATEST = SolrVersion.valueOf(LATEST_STRING);
+
+  /**
+   * Create a SolrVersion instance from string value. The string must comply 
to the SemVer spec
+   */
+  public static SolrVersion valueOf(String version) {
+    return new SolrVersion(Version.valueOf(version));
+  }
+
+  /**
+   * Create a SolrVersion instance from set of integer values. Must comply to 
the SemVer spec
+   */
+  public static SolrVersion forIntegers(int major, int minor, int patch) {
+    return new SolrVersion(Version.forIntegers(major, minor, patch));
+  }
+
+  /**
+   * Return version as plain SemVer string, e.g. "9.0.1"
+   */
+  @Override
+  public String toString() {
+    return version.toString();
+  }
+
+  public boolean greaterThan(SolrVersion other) {
+    return version.greaterThan(other.version);
+  }
+
+  public boolean greaterThanOrEqualTo(SolrVersion other) {
+    return version.greaterThanOrEqualTo(other.version);
+  }
+
+  public boolean lessThan(SolrVersion other) {
+    return version.lessThan(other.version);
+  }
+
+  public boolean lessThanOrEqualTo(SolrVersion other) {
+    return version.lessThanOrEqualTo(other.version);
+  }
+
+  /**
+   * Returns true if this version satisfies the provided <a 
href="https://devhints.io/semver";>SemVer Expression</a>
+   * @param semVerExpression the expression to test
+   * @throws InvalidSemVerExpressionException if the SemVer expression is 
invalid
+   */
+  public boolean satisfies(String semVerExpression) {
+    try {
+      return 
ExpressionParser.newInstance().parse(semVerExpression).interpret(version);
+    } catch (ParseException parseException) {
+      throw new InvalidSemVerExpressionException();
+    }
+  }
+
+  public int getMajorVersion() {
+    return version.getMajorVersion();
+  }
+
+  public int getMinorVersion() {
+    return version.getMinorVersion();
+  }
+
+  public int getPatchVersion() {
+    return version.getPatchVersion();
+  }
+
+  public String getPrereleaseVersion() {
+    return version.getPreReleaseVersion();
+  }
+
+  // Private constructor. Should be instantiated from static factory methods
+  private SolrVersion(Version version) {
+    this.version = version;
+  }
+
+  @Override
+  public int hashCode() {
+    return version.hashCode();
+  }
+
+  @Override
+  public int compareTo(SolrVersion other) {
+    return version.compareTo(other.version);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (!(other instanceof SolrVersion)) {
+      return false;
+    }
+    return compareTo((SolrVersion) other) == 0;
+  }
+
+  public static class InvalidSemVerExpressionException extends SolrException {
+    public InvalidSemVerExpressionException() {
+      super(ErrorCode.BAD_REQUEST, "Invalid SemVer expression");
+    }
+  }
+}
\ No newline at end of file
diff --git a/solr/core/src/test/org/apache/solr/util/TestSolrVersion.java 
b/solr/core/src/test/org/apache/solr/util/TestSolrVersion.java
new file mode 100644
index 0000000..ae3d520
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/TestSolrVersion.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util;
+
+import com.github.zafarkhaja.semver.ParseException;
+import org.apache.solr.SolrTestCase;
+
+public class TestSolrVersion extends SolrTestCase {
+  private static final SolrVersion SOLR_9_0_1 = SolrVersion.valueOf("9.0.1");
+
+  public void testToString() {
+    assertEquals("9.0.0", SolrVersion.forIntegers(9, 0, 0).toString());
+  }
+
+  public void testVersionComponents() {
+    SolrVersion v9_0_1_rc1 = SolrVersion.valueOf("9.0.1-rc1");
+    assertEquals("9.0.1-rc1", v9_0_1_rc1.toString());
+    assertEquals(9, v9_0_1_rc1.getMajorVersion());
+    assertEquals(0, v9_0_1_rc1.getMinorVersion());
+    assertEquals(1, v9_0_1_rc1.getPatchVersion());
+    assertEquals("rc1", v9_0_1_rc1.getPrereleaseVersion());
+  }
+
+  public void testLatestInitialized() {
+    
assertTrue(SolrVersion.LATEST.greaterThanOrEqualTo(SolrVersion.valueOf("9.0.0")));
+  }
+
+  public void testForwardsCompatibility() {
+    
assertTrue(SolrVersion.valueOf("9.10.20").greaterThanOrEqualTo(SolrVersion.forIntegers(9,
 0, 0)));
+  }
+
+  public void testParseExceptions() {
+    expectThrows(
+        ParseException.class,
+        () -> SolrVersion.valueOf("SOLR_7_0_0"));
+  }
+
+  public void testSatisfies() {
+    assertTrue(SOLR_9_0_1.satisfies("~9.0"));
+    assertTrue(SOLR_9_0_1.satisfies("9.x"));
+    assertTrue(SOLR_9_0_1.satisfies("9"));
+    assertTrue(SOLR_9_0_1.satisfies("<9.1"));
+    assertTrue(SOLR_9_0_1.satisfies(">9.0"));
+    assertFalse(SOLR_9_0_1.satisfies("8.x"));
+  }
+
+  public void testSatisfiesParseFailure() {
+    assertThrows(SolrVersion.InvalidSemVerExpressionException.class, () ->
+        SOLR_9_0_1.satisfies(":"));
+  }
+}

Reply via email to