Chromium Code Reviews| Index: lib/src/version_union.dart |
| diff --git a/lib/src/version_union.dart b/lib/src/version_union.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8ced188fc97831cb50bff6f5d7ece1081d467932 |
| --- /dev/null |
| +++ b/lib/src/version_union.dart |
| @@ -0,0 +1,186 @@ |
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library pub_semver.src.version_union; |
| + |
| +import 'dart:collection'; |
| + |
| +import 'package:collection/collection.dart'; |
| + |
| +import 'utils.dart'; |
| +import 'version.dart'; |
| +import 'version_constraint.dart'; |
| +import 'version_range.dart'; |
| + |
| +/// A (package-private) version constraint representing a union of multiple |
| +/// disjoint version constraints. |
| +/// |
| +/// An instance of this will only be created if the version can't be represented |
| +/// as a non-compound value. |
| +class VersionUnion implements VersionConstraint { |
| + /// The constraints that compose this union. |
| + /// |
| + /// This list has several invariants: |
| + /// |
| + /// * It contains only [Version]s and [VersionRange]s. |
| + /// * Its contents are sorted from lowest to highest matched versions. |
| + /// * Its contents are disjoint and non-adjacent. In other words, for any two |
| + /// constraints next to each other in the list, there's some version between |
| + /// those constraints that they don't match. |
| + final List<VersionConstraint> constraints; |
| + |
| + bool get isEmpty => false; |
| + |
| + bool get isAny => false; |
| + |
| + /// Returns the union of [constraints]. |
| + /// |
| + /// This ensures that an actual [VersionUnion] is only returned if necessary. |
| + /// It also takes care of sorting and merging the constraints to ensure that |
| + /// they're disjoint. |
| + static VersionConstraint create(Iterable<VersionConstraint> constraints) { |
| + constraints = constraints.expand((constraint) { |
|
Bob Nystrom
2015/05/05 20:49:09
Instead of reassigning, how about:
var flattened
nweiz
2015/05/05 22:52:34
Done.
|
| + if (constraint.isEmpty) return []; |
| + if (constraint is VersionUnion) return constraint.constraints; |
| + return [constraint]; |
| + }).toList(); |
| + |
| + if (constraints.isEmpty) return VersionConstraint.empty; |
| + |
| + if (constraints.any((constraint) => constraint.isAny)) { |
| + return VersionConstraint.any; |
| + } |
| + |
| + // Only allow Versions and VersionRanges here so we can more easily reason |
| + // about everything in [constraints]. _EmptyVersions and VersionUnions are |
| + // filtered out above. |
| + for (var constraint in constraints) { |
| + if (constraint is Version || constraint is VersionRange) continue; |
| + throw new ArgumentError('Unknown VersionConstraint type $constraint.'); |
| + } |
| + |
| + (constraints as List).sort(compareMax); |
| + |
| + var merged = []; |
| + for (var constraint in constraints) { |
|
Bob Nystrom
2015/05/05 20:49:09
// If this constraint doesn't touch the previous o
nweiz
2015/05/05 22:52:34
Done.
|
| + if (merged.isEmpty || |
| + (!merged.last.allowsAny(constraint) && |
| + !areAdjacent(merged.last, constraint))) { |
| + merged.add(constraint); |
| + } else { |
| + merged[merged.length - 1] = merged.last.union(constraint); |
| + } |
| + } |
| + |
| + if (merged.length == 1) return merged.single; |
| + return new VersionUnion._(merged); |
| + } |
| + |
| + VersionUnion._(List<VersionConstraint> constraints) |
| + : constraints = new UnmodifiableListView(constraints); |
|
Bob Nystrom
2015/05/05 20:49:09
If this is a package private class, is this worth
nweiz
2015/05/05 22:52:34
Probably not. Removed.
|
| + |
| + bool allows(Version version) => |
| + constraints.any((constraint) => constraint.allows(version)); |
| + |
| + bool allowsAll(VersionConstraint other) { |
| + var ourConstraints = constraints.iterator; |
| + var theirConstraints = _constraintsFor(other).iterator; |
| + |
| + // Because both lists of constraints are ordered by minimum version, we can |
| + // safely move through them linearly here. |
| + ourConstraints.moveNext(); |
| + theirConstraints.moveNext(); |
| + while (ourConstraints.current != null && theirConstraints.current != null) { |
|
Bob Nystrom
2015/05/05 20:49:09
Checking for .current == null feels sketchy to me.
nweiz
2015/05/05 22:52:34
I don't think there's a clean way to do that. Even
|
| + if (ourConstraints.current.allowsAll(theirConstraints.current)) { |
| + theirConstraints.moveNext(); |
| + } else { |
| + ourConstraints.moveNext(); |
| + } |
| + } |
| + |
| + // If our constraints have allowed all of their constraints, we'll have |
| + // consumed all of them. |
| + return theirConstraints.current == null; |
| + } |
| + |
| + bool allowsAny(VersionConstraint other) { |
| + var ourConstraints = constraints.iterator; |
| + var theirConstraints = _constraintsFor(other).iterator; |
| + |
| + // Because both lists of constraints are ordered by minimum version, we can |
| + // safely move through them linearly here. |
| + ourConstraints.moveNext(); |
| + theirConstraints.moveNext(); |
| + while (ourConstraints.current != null && theirConstraints.current != null) { |
| + if (ourConstraints.current.allowsAny(theirConstraints.current)) { |
| + return true; |
| + } |
| + |
| + // Move the constraint with the higher max value forward. This ensures |
| + // that we keep both lists in sync as much as possible. |
| + if (compareMax(ourConstraints.current, theirConstraints.current) < 0) { |
| + ourConstraints.moveNext(); |
| + } else { |
| + theirConstraints.moveNext(); |
| + } |
| + } |
| + |
| + return false; |
| + } |
| + |
| + VersionConstraint intersect(VersionConstraint other) { |
| + var ourConstraints = constraints.iterator; |
| + var theirConstraints = _constraintsFor(other).iterator; |
| + |
| + // Because both lists of constraints are ordered by minimum version, we can |
| + // safely move through them linearly here. |
| + var newConstraints = []; |
| + ourConstraints.moveNext(); |
| + theirConstraints.moveNext(); |
| + while (ourConstraints.current != null && theirConstraints.current != null) { |
| + var intersection = ourConstraints.current |
| + .intersect(theirConstraints.current); |
| + |
| + if (!intersection.isEmpty) newConstraints.add(intersection); |
| + |
| + // Move the constraint with the higher max value forward. This ensures |
| + // that we keep both lists in sync as much as possible, and that large |
| + // constraints have a change to match multiple small constraints that they |
|
Bob Nystrom
2015/05/05 20:49:09
"change" -> "chance".
nweiz
2015/05/05 22:52:34
Done.
|
| + // contain. |
| + if (compareMax(ourConstraints.current, theirConstraints.current) < 0) { |
| + ourConstraints.moveNext(); |
| + } else { |
| + theirConstraints.moveNext(); |
| + } |
| + } |
| + |
| + if (newConstraints.isEmpty) return VersionConstraint.empty; |
| + if (newConstraints.length == 1) return newConstraints.single; |
| + |
| + return new VersionUnion._(newConstraints); |
| + } |
| + |
| + /// Returns [constraint] as a list of constraints. |
| + /// |
| + /// This is used to normalize constraints of various types. |
| + List<VersionConstraint> _constraintsFor(VersionConstraint constraint) { |
|
Bob Nystrom
2015/05/05 20:49:09
Make this a function?
nweiz
2015/05/05 22:52:34
What do you mean?
Bob Nystrom
2015/05/06 17:30:58
It's an instance method here, but it doesn't acces
nweiz
2015/05/06 18:18:58
We don't typically do this for private methods in
|
| + if (constraint.isEmpty) return []; |
| + if (constraint is VersionUnion) return constraint.constraints; |
| + if (constraint is Version) return [constraint]; |
| + if (constraint is VersionRange) return [constraint]; |
| + throw new ArgumentError('Unknown VersionConstraint type $constraint.'); |
| + } |
| + |
| + VersionConstraint union(VersionConstraint other) => |
| + new VersionConstraint.unionOf([this, other]); |
| + |
| + bool operator ==(other) { |
| + if (other is! VersionUnion) return false; |
| + return const ListEquality().equals(constraints, other.constraints); |
| + } |
| + |
| + int get hashCode => const ListEquality().hash(constraints); |
| + |
| + String toString() => constraints.join(" or "); |
| +} |