com.helger.commons.version.VersionRange Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-commons Show documentation
Show all versions of ph-commons Show documentation
Java 1.8+ Library with tons of utility classes required in all projects
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed 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 com.helger.commons.version;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.compare.IComparable;
import com.helger.commons.equals.EqualsHelper;
import com.helger.commons.hashcode.HashCodeGenerator;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
/**
* This class represents a range of versions. Each range needs at least a lower
* bound but can as well have an upper bound. See OSGi v4 reference 3.2.5
*
* @author Philip Helger
*/
@Immutable
public final class VersionRange implements IComparable
{
/**
* Default version range string.
*/
public static final String DEFAULT_VERSION_RANGE_STRING = "[0)";
/**
* <= instead of < ???
*/
private final boolean m_bIncludeFloor;
/**
* floor version
*/
private final Version m_aFloorVersion;
/**
* >= instead of > ???
*/
private final boolean m_bIncludeCeil;
/**
* ceiling version
*/
private final Version m_aCeilVersion;
/**
* Construct a version range object from a string.
* Examples:
*
* - [1.2.3, 4.5.6) -- 1.2.3 <= x < 4.5.6
* - [1.2.3, 4.5.6] -- 1.2.3 <= x <= 4.5.6
* - (1.2.3, 4.5.6) -- 1.2.3 < x < 4.5.6
* - (1.2.3, 4.5.6] -- 1.2.3 < x <= 4.5.6
* - 1.2.3 -- 1.2.3 <= x
* - [1.2.3 -- 1.2.3 <= x
* - (1.2.3 -- 1.2.3 < x
* - null -- 0.0.0 <= x
* - 1, 4 -- 1 <= x <= 4
*
*
* @param sVersionString
* the version range in a string format as depicted above
* @return The parsed {@link VersionRange} object
* @throws IllegalArgumentException
* if the floor version is < than the ceiling version
*/
@Nonnull
public static VersionRange parse (@Nullable final String sVersionString)
{
final String s = sVersionString == null ? "" : sVersionString.trim ();
if (s.length () == 0)
{
// empty string == range [0.0, infinity)
return new VersionRange (Version.DEFAULT_VERSION, true, null, false);
}
Version aFloorVersion;
boolean bIncludeFloor;
Version aCeilVersion;
boolean bIncludeCeil;
int i = 0;
// parse initial token
if (s.charAt (i) == '[')
{
bIncludeFloor = true;
i++;
}
else
if (s.charAt (i) == '(')
{
bIncludeFloor = false;
i++;
}
else
bIncludeFloor = true;
// check last token
int j = 0;
if (StringHelper.endsWith (s, ']'))
{
bIncludeCeil = true;
j++;
}
else
if (StringHelper.endsWith (s, ')'))
{
bIncludeCeil = false;
j++;
}
else
bIncludeCeil = false;
// get length of version stuff
final int nRestLen = s.length () - i - j;
if (nRestLen == 0)
{
// only delimiter braces present?
aFloorVersion = Version.DEFAULT_VERSION;
aCeilVersion = null;
}
else
{
final String [] parts = StringHelper.getExplodedArray (',', s.substring (i, s.length () - j));
final String sFloor = parts[0].trim ();
final String sCeiling = parts.length > 1 ? parts[1].trim () : null;
// get floor version
aFloorVersion = Version.parse (sFloor);
if (StringHelper.hasNoText (sCeiling))
aCeilVersion = null;
else
aCeilVersion = Version.parse (sCeiling);
}
// check if floor <= ceil
if (aCeilVersion != null && aFloorVersion.compareTo (aCeilVersion) > 0)
throw new IllegalArgumentException ("Floor version may not be greater than the ceiling version!");
return new VersionRange (aFloorVersion, bIncludeFloor, aCeilVersion, bIncludeCeil);
}
/**
* Create a new version range depicted by two versions, assuming that both the
* floor and the ceiling version should be included meaning we have an
* inclusive interval.
*
* @param aFloorVersion
* the floor version of the range - may not be null
* @param aCeilingVersion
* the ceiling version of the range - may be null
* @throws IllegalArgumentException
* if the floor version to be used is > the ceiling version or if
* the floor version is null.
*/
public VersionRange (@Nonnull final Version aFloorVersion, @Nullable final Version aCeilingVersion)
{
this (aFloorVersion, true, aCeilingVersion, true);
}
/**
* Create a new version range depicted by two versions.
*
* @param aFloorVersion
* the floor version of the range - may not be null
* @param bIncludeFloorVersion
* if true, a >= comparison is used on the version number, else a
* > comparison is used
* @param aCeilingVersion
* the ceiling version of the range - may be null
* @param bIncludeCeilingVersion
* if true, a <= comparison is used on the version number, else a
* < comparison is used
* @throws IllegalArgumentException
* if the floor version to be used is > the ceiling version or if
* the floor version is null.
*/
public VersionRange (@Nonnull final Version aFloorVersion,
final boolean bIncludeFloorVersion,
@Nullable final Version aCeilingVersion,
final boolean bIncludeCeilingVersion)
{
ValueEnforcer.notNull (aFloorVersion, "FloorVersion");
// set values
m_aFloorVersion = aFloorVersion;
m_bIncludeFloor = bIncludeFloorVersion;
m_aCeilVersion = aCeilingVersion;
m_bIncludeCeil = bIncludeCeilingVersion;
// check if floor <= ceil
if (m_aCeilVersion != null && m_aFloorVersion.compareTo (m_aCeilVersion) > 0)
throw new IllegalArgumentException ("Floor version may not be greater than the ceiling version!");
}
public boolean isIncludingFloor ()
{
return m_bIncludeFloor;
}
@Nullable
public Version getFloorVersion ()
{
return m_aFloorVersion;
}
public boolean isIncludingCeil ()
{
return m_bIncludeCeil;
}
@Nullable
public Version getCeilVersion ()
{
return m_aCeilVersion;
}
public boolean versionMatches (@Nonnull final Version rhs)
{
// returns -1 if floor < rhs
// -> error
int i = m_aFloorVersion.compareTo (rhs);
if (m_bIncludeFloor ? i > 0 : i >= 0)
return false;
// check ceiling version
if (m_aCeilVersion != null)
{
i = m_aCeilVersion.compareTo (rhs);
if (m_bIncludeCeil ? i < 0 : i <= 0)
return false;
}
return true;
}
/**
* Compare this version range to another version range. Returns -1 if this is
* < than the passed version or +1 if this is > the passed version range
*
* @param rhs
* the version range to compare to
* @return 0 if the passed version range is equal to this version range
* -1 if the floor version of this is < than the floor version of
* the passed version range.
* -1 if the floor versions are equal but the ceiling version of this
* has a lower upper bound than the passed version range
* +1 if the floor version of this is > than the floor version of
* the passed version range.
* +1 if the floor versions are equal but the ceiling version of this
* has a higher upper bound than the passed version range
*/
public int compareTo (@Nonnull final VersionRange rhs)
{
int i = m_aFloorVersion.compareTo (rhs.m_aFloorVersion);
if (i == 0)
{
if (m_bIncludeFloor && !rhs.m_bIncludeFloor)
{
// this < rhs
i = -1;
}
else
if (!m_bIncludeFloor && rhs.m_bIncludeFloor)
{
// this > rhs
i = +1;
}
if (i == 0)
{
// compare ceiling
if (m_aCeilVersion != null && rhs.m_aCeilVersion == null)
i = -1;
else
if (m_aCeilVersion == null && rhs.m_aCeilVersion != null)
i = +1;
else
if (m_aCeilVersion != null && rhs.m_aCeilVersion != null)
i = m_aCeilVersion.compareTo (rhs.m_aCeilVersion);
// else i stays 0 if both are null
if (i == 0)
{
if (m_bIncludeCeil && !rhs.m_bIncludeCeil)
i = +1;
else
if (!m_bIncludeCeil && rhs.m_bIncludeCeil)
i = -1;
}
}
}
return i;
}
/**
* Converts the version range to a string. The brackets whether floor or
* ceiling version should be included or not is always prepended and appended.
* If a ceiling version is present, the ceiling version is appended with a
* single comma as a delimiter.
* Example return: "[1.2.3,4.5.6)"
*
* @return The version range in a parseable string format.
*/
@Nonnull
public String getAsString ()
{
return getAsString (Version.DEFAULT_PRINT_ZERO_ELEMENTS);
}
/**
* Converts the version range to a string. The brackets whether floor or
* ceiling version should be included or not is always prepended and appended.
* If a ceiling version is present, the ceiling version is appended with a
* single comma as a delimiter.
* Example return: "[1.2.3,4.5.6)"
*
* @param bPrintZeroElements
* If true
than trailing zeroes are printed, otherwise
* printed zeroes are not printed.
* @return Never null
.
*/
@Nonnull
public String getAsString (final boolean bPrintZeroElements)
{
// special handling if no ceiling version is present
final StringBuilder aSB = new StringBuilder (m_bIncludeFloor ? "[" : "(");
aSB.append (m_aFloorVersion.getAsString (bPrintZeroElements));
if (m_aCeilVersion != null)
{
aSB.append (',').append (m_aCeilVersion.getAsString (bPrintZeroElements));
}
return aSB.append (m_bIncludeCeil ? ']' : ')').toString ();
}
@Override
public boolean equals (final Object o)
{
if (o == this)
return true;
if (o == null || !getClass ().equals (o.getClass ()))
return false;
final VersionRange rhs = (VersionRange) o;
return m_bIncludeFloor == rhs.m_bIncludeFloor &&
m_aFloorVersion.equals (rhs.m_aFloorVersion) &&
m_bIncludeCeil == rhs.m_bIncludeCeil &&
EqualsHelper.equals (m_aCeilVersion, rhs.m_aCeilVersion);
}
@Override
public int hashCode ()
{
return new HashCodeGenerator (this).append (m_aFloorVersion)
.append (m_bIncludeFloor)
.append (m_aCeilVersion)
.append (m_bIncludeCeil)
.getHashCode ();
}
@Override
public String toString ()
{
return new ToStringGenerator (this).append ("floorVersion", m_aFloorVersion)
.append ("inclFloor", m_bIncludeFloor)
.append ("ceilVersion", m_aCeilVersion)
.append ("inclCeil", m_bIncludeCeil)
.getToString ();
}
}