Skip to content
This repository was archived by the owner on Jul 30, 2019. It is now read-only.

Commit 7abdb49

Browse files
Laszlo HordosLaszlo Hordos
Laszlo Hordos
authored and
Laszlo Hordos
committed
OPENICF-63 / CR-1163 - Add new VersionRange class
1 parent 067678b commit 7abdb49

File tree

3 files changed

+465
-0
lines changed

3 files changed

+465
-0
lines changed

Framework/Common.cs

+335
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
using System;
2424
using System.Reflection;
25+
using System.Text;
2526
using System.Collections.Generic;
2627
using Org.IdentityConnectors.Common;
2728
using Org.IdentityConnectors.Common.Security;
@@ -379,4 +380,338 @@ public static Version GetFrameworkVersion()
379380
return Assembly.GetExecutingAssembly().GetName().Version;
380381
}
381382
}
383+
384+
/// <summary>
385+
/// A version range is an interval describing a set of <seealso cref="Version versions"/>.
386+
/// <p/>
387+
/// A range has a left (lower) endpoint and a right (upper) endpoint. Each
388+
/// endpoint can be open (excluded from the set) or closed (included in the set).
389+
///
390+
/// <p>
391+
/// {@code VersionRange} objects are immutable.
392+
///
393+
/// @author Laszlo Hordos
394+
/// @Immutable
395+
/// </summary>
396+
public class VersionRange
397+
{
398+
399+
/// <summary>
400+
/// The left endpoint is open and is excluded from the range.
401+
/// <p>
402+
/// The value of {@code LEFT_OPEN} is {@code '('}.
403+
/// </summary>
404+
public const char LEFT_OPEN = '(';
405+
/// <summary>
406+
/// The left endpoint is closed and is included in the range.
407+
/// <p>
408+
/// The value of {@code LEFT_CLOSED} is {@code '['}.
409+
/// </summary>
410+
public const char LEFT_CLOSED = '[';
411+
/// <summary>
412+
/// The right endpoint is open and is excluded from the range.
413+
/// <p>
414+
/// The value of {@code RIGHT_OPEN} is {@code ')'}.
415+
/// </summary>
416+
public const char RIGHT_OPEN = ')';
417+
/// <summary>
418+
/// The right endpoint is closed and is included in the range.
419+
/// <p>
420+
/// The value of {@code RIGHT_CLOSED} is {@code ']'}.
421+
/// </summary>
422+
public const char RIGHT_CLOSED = ']';
423+
424+
private const string ENDPOINT_DELIMITER = ",";
425+
426+
private readonly Version floorVersion;
427+
private readonly bool isFloorInclusive;
428+
private readonly Version ceilingVersion;
429+
private readonly bool isCeilingInclusive;
430+
private readonly bool empty;
431+
432+
/// <summary>
433+
/// Parse version component into a Version.
434+
/// </summary>
435+
/// <param name="version">
436+
/// version component string </param>
437+
/// <param name="range">
438+
/// Complete range string for exception message, if any </param>
439+
/// <returns> Version </returns>
440+
private static Version parseVersion(string version, string range)
441+
{
442+
try
443+
{
444+
return Version.Parse(version);
445+
}
446+
catch (System.ArgumentException e)
447+
{
448+
throw new System.ArgumentException("invalid range \"" + range + "\": " + e.Message, e);
449+
}
450+
}
451+
452+
/// <summary>
453+
/// Creates a version range from the specified string.
454+
///
455+
/// <p>
456+
/// Version range string grammar:
457+
///
458+
/// <pre>
459+
/// range ::= interval | at least
460+
/// interval ::= ( '[' | '(' ) left ',' right ( ']' | ')' )
461+
/// left ::= version
462+
/// right ::= version
463+
/// at least ::= version
464+
/// </pre>
465+
/// </summary>
466+
/// <param name="range">
467+
/// String representation of the version range. The versions in
468+
/// the range must contain no whitespace. Other whitespace in the
469+
/// range string is ignored. </param>
470+
/// <exception cref="IllegalArgumentException">
471+
/// If {@code range} is improperly formatted. </exception>
472+
public static VersionRange Parse(string range)
473+
{
474+
Assertions.BlankCheck(range, "range");
475+
int idx = range.IndexOf(ENDPOINT_DELIMITER);
476+
// Check if the version is an interval.
477+
if (idx > 1 && idx == range.LastIndexOf(ENDPOINT_DELIMITER))
478+
{
479+
string vlo = range.Substring(0, idx).Trim();
480+
string vhi = range.Substring(idx + 1).Trim();
481+
482+
bool isLowInclusive = true;
483+
bool isHighInclusive = true;
484+
if (vlo[0] == LEFT_OPEN)
485+
{
486+
isLowInclusive = false;
487+
}
488+
else if (vlo[0] != LEFT_CLOSED)
489+
{
490+
throw new System.ArgumentException("invalid range \"" + range + "\": invalid format");
491+
}
492+
vlo = vlo.Substring(1).Trim();
493+
494+
if (vhi[vhi.Length - 1] == RIGHT_OPEN)
495+
{
496+
isHighInclusive = false;
497+
}
498+
else if (vhi[vhi.Length - 1] != RIGHT_CLOSED)
499+
{
500+
throw new System.ArgumentException("invalid range \"" + range + "\": invalid format");
501+
}
502+
vhi = vhi.Substring(0, vhi.Length - 1).Trim();
503+
504+
return new VersionRange(parseVersion(vlo, range), isLowInclusive, parseVersion(vhi, range), isHighInclusive);
505+
}
506+
else if (idx == -1)
507+
{
508+
return new VersionRange(VersionRange.parseVersion(range.Trim(), range), true, null, false);
509+
}
510+
else
511+
{
512+
throw new System.ArgumentException("invalid range \"" + range + "\": invalid format");
513+
}
514+
}
515+
516+
public VersionRange(Version low, bool isLowInclusive, Version high, bool isHighInclusive)
517+
{
518+
Assertions.NullCheck(low, "floorVersion");
519+
floorVersion = low;
520+
isFloorInclusive = isLowInclusive;
521+
ceilingVersion = high;
522+
isCeilingInclusive = isHighInclusive;
523+
empty = Empty0;
524+
}
525+
526+
public virtual Version Floor
527+
{
528+
get
529+
{
530+
return floorVersion;
531+
}
532+
}
533+
534+
public virtual bool FloorInclusive
535+
{
536+
get
537+
{
538+
return isFloorInclusive;
539+
}
540+
}
541+
542+
public virtual Version Ceiling
543+
{
544+
get
545+
{
546+
return ceilingVersion;
547+
}
548+
}
549+
550+
public virtual bool CeilingInclusive
551+
{
552+
get
553+
{
554+
return isCeilingInclusive;
555+
}
556+
}
557+
558+
public virtual bool IsInRange(Version version)
559+
{
560+
if (empty)
561+
{
562+
return false;
563+
}
564+
if (floorVersion.CompareTo(version) >= (isFloorInclusive ? 1 : 0))
565+
{
566+
return false;
567+
}
568+
if (ceilingVersion == null)
569+
{
570+
return true;
571+
}
572+
return ceilingVersion.CompareTo(version) >= (isCeilingInclusive ? 0 : 1);
573+
574+
}
575+
576+
/// <summary>
577+
/// Returns whether this version range contains only a single version.
578+
/// </summary>
579+
/// <returns> {@code true} if this version range contains only a single
580+
/// version; {@code false} otherwise. </returns>
581+
public virtual bool Exact
582+
{
583+
get
584+
{
585+
if (empty)
586+
{
587+
return false;
588+
}
589+
else if (ceilingVersion == null)
590+
{
591+
return true;
592+
}
593+
if (isFloorInclusive)
594+
{
595+
if (isCeilingInclusive)
596+
{
597+
// [f,c]: exact if f == c
598+
return floorVersion.Equals(ceilingVersion);
599+
}
600+
else
601+
{
602+
// [f,c): exact if f++ >= c
603+
Version adjacent1 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 1);
604+
return adjacent1.CompareTo(ceilingVersion) >= 0;
605+
}
606+
}
607+
else
608+
{
609+
if (isCeilingInclusive)
610+
{
611+
// (f,c] is equivalent to [f++,c]: exact if f++ == c
612+
Version adjacent1 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 1);
613+
return adjacent1.Equals(ceilingVersion);
614+
}
615+
else
616+
{
617+
// (f,c) is equivalent to [f++,c): exact if (f++)++ >=c
618+
Version adjacent2 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 2);
619+
return adjacent2.CompareTo(ceilingVersion) >= 0;
620+
}
621+
}
622+
}
623+
}
624+
625+
/// <summary>
626+
/// Returns whether this version range is empty. A version range is empty if
627+
/// the set of versions defined by the interval is empty.
628+
/// </summary>
629+
/// <returns> {@code true} if this version range is empty; {@code false}
630+
/// otherwise. </returns>
631+
public virtual bool Empty
632+
{
633+
get
634+
{
635+
return empty;
636+
}
637+
}
638+
639+
/// <summary>
640+
/// Internal isEmpty behavior.
641+
/// </summary>
642+
/// <returns> {@code true} if this version range is empty; {@code false}
643+
/// otherwise. </returns>
644+
private bool Empty0
645+
{
646+
get
647+
{
648+
if (ceilingVersion == null) // infinity
649+
{
650+
return false;
651+
}
652+
int comparison = floorVersion.CompareTo(ceilingVersion);
653+
if (comparison == 0) // endpoints equal
654+
{
655+
return !isFloorInclusive || !isCeilingInclusive;
656+
}
657+
return comparison > 0; // true if left > right
658+
}
659+
}
660+
661+
public virtual bool Equals(object obj)
662+
{
663+
if (obj == null)
664+
{
665+
return false;
666+
}
667+
if (this.GetType() != obj.GetType())
668+
{
669+
return false;
670+
}
671+
VersionRange other = (VersionRange)obj;
672+
if (floorVersion != other.floorVersion && (floorVersion == null || !floorVersion.Equals(other.floorVersion)))
673+
{
674+
return false;
675+
}
676+
if (isFloorInclusive != other.isFloorInclusive)
677+
{
678+
return false;
679+
}
680+
if (ceilingVersion != other.ceilingVersion && (ceilingVersion == null || !ceilingVersion.Equals(other.ceilingVersion)))
681+
{
682+
return false;
683+
}
684+
if (isCeilingInclusive != other.isCeilingInclusive)
685+
{
686+
return false;
687+
}
688+
return true;
689+
}
690+
691+
public override int GetHashCode()
692+
{
693+
int result = floorVersion.GetHashCode();
694+
result = 31 * result + (isFloorInclusive ? 1 : 0);
695+
result = 31 * result + (ceilingVersion != null ? ceilingVersion.GetHashCode() : 0);
696+
result = 31 * result + (isCeilingInclusive ? 1 : 0);
697+
return result;
698+
}
699+
700+
public virtual string ToString()
701+
{
702+
if (ceilingVersion != null)
703+
{
704+
StringBuilder sb = new StringBuilder();
705+
sb.Append(isFloorInclusive ? LEFT_CLOSED : LEFT_OPEN);
706+
sb.Append(floorVersion.ToString()).Append(ENDPOINT_DELIMITER).Append(ceilingVersion.ToString());
707+
sb.Append(isCeilingInclusive ? RIGHT_CLOSED : RIGHT_OPEN);
708+
return sb.ToString();
709+
}
710+
else
711+
{
712+
return floorVersion.ToString();
713+
}
714+
}
715+
}
716+
382717
}

FrameworkTests/FrameworkTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<Compile Include="TestHelperTests.cs" />
9393
<Compile Include="TestUtil.cs" />
9494
<Compile Include="UpdateImplTests.cs" />
95+
<Compile Include="VersionRangeTests.cs" />
9596
<None Include="app.config">
9697
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
9798
</None>

0 commit comments

Comments
 (0)