OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'package:collection/collection.dart'; | 5 import 'package:collection/collection.dart'; |
6 | 6 |
7 import 'utils.dart'; | 7 import 'utils.dart'; |
8 import 'version.dart'; | 8 import 'version.dart'; |
9 import 'version_constraint.dart'; | 9 import 'version_constraint.dart'; |
10 import 'version_range.dart'; | 10 import 'version_range.dart'; |
11 | 11 |
12 /// A (package-private) version constraint representing a union of multiple | 12 /// A version constraint representing a union of multiple disjoint version |
13 /// disjoint version constraints. | 13 /// ranges. |
14 /// | 14 /// |
15 /// An instance of this will only be created if the version can't be represented | 15 /// An instance of this will only be created if the version can't be represented |
16 /// as a non-compound value. | 16 /// as a non-compound value. |
17 class VersionUnion implements VersionConstraint { | 17 class VersionUnion implements VersionConstraint { |
18 /// The constraints that compose this union. | 18 /// The constraints that compose this union. |
19 /// | 19 /// |
20 /// This list has two invariants: | 20 /// This list has two invariants: |
21 /// | 21 /// |
22 /// * Its contents are sorted from lowest to highest matched versions. | 22 /// * Its contents are sorted from lowest to highest matched versions. |
23 /// * Its contents are disjoint and non-adjacent. In other words, for any two | 23 /// * Its contents are disjoint and non-adjacent. In other words, for any two |
24 /// constraints next to each other in the list, there's some version between | 24 /// constraints next to each other in the list, there's some version between |
25 /// those constraints that they don't match. | 25 /// those constraints that they don't match. |
26 final List<VersionRange> constraints; | 26 final List<VersionRange> ranges; |
27 | 27 |
28 bool get isEmpty => false; | 28 bool get isEmpty => false; |
29 | 29 |
30 bool get isAny => false; | 30 bool get isAny => false; |
31 | 31 |
32 /// Returns the union of [constraints]. | 32 /// Creates a union from a list of ranges with no pre-processing. |
33 /// | 33 /// |
34 /// This ensures that an actual [VersionUnion] is only returned if necessary. | 34 /// It's up to the caller to ensure that the invariants described in [ranges] |
35 /// It also takes care of sorting and merging the constraints to ensure that | 35 /// are maintained. They are not verified by this constructor. To |
36 /// they're disjoint. | 36 /// automatically ensure that they're maintained, use [new |
37 static VersionConstraint create(Iterable<VersionConstraint> constraints) { | 37 /// VersionConstraint.unionOf] instead. |
38 var flattened = constraints.expand((constraint) { | 38 VersionUnion.fromRanges(this.ranges); |
39 if (constraint.isEmpty) return []; | |
40 if (constraint is VersionUnion) return constraint.constraints; | |
41 return [constraint]; | |
42 }).toList(); | |
43 | 39 |
44 if (flattened.isEmpty) return VersionConstraint.empty; | 40 bool allows(Version version) => |
| 41 ranges.any((constraint) => constraint.allows(version)); |
45 | 42 |
46 if (flattened.any((constraint) => constraint.isAny)) { | 43 bool allowsAll(VersionConstraint other) { |
47 return VersionConstraint.any; | 44 var ourRanges = ranges.iterator; |
48 } | 45 var theirRanges = _rangesFor(other).iterator; |
49 | 46 |
50 // Only allow Versions and VersionRanges here so we can more easily reason | 47 // Because both lists of ranges are ordered by minimum version, we can |
51 // about everything in [flattened]. _EmptyVersions and VersionUnions are | 48 // safely move through them linearly here. |
52 // filtered out above. | 49 ourRanges.moveNext(); |
53 for (var constraint in flattened) { | 50 theirRanges.moveNext(); |
54 if (constraint is VersionRange) continue; | 51 while (ourRanges.current != null && theirRanges.current != null) { |
55 throw new ArgumentError('Unknown VersionConstraint type $constraint.'); | 52 if (ourRanges.current.allowsAll(theirRanges.current)) { |
56 } | 53 theirRanges.moveNext(); |
57 | |
58 flattened.sort(compareMax); | |
59 | |
60 var merged = <VersionRange>[]; | |
61 for (var constraint in flattened) { | |
62 // Merge this constraint with the previous one, but only if they touch. | |
63 if (merged.isEmpty || | |
64 (!merged.last.allowsAny(constraint) && | |
65 !areAdjacent(merged.last, constraint))) { | |
66 merged.add(constraint); | |
67 } else { | 54 } else { |
68 merged[merged.length - 1] = merged.last.union(constraint); | 55 ourRanges.moveNext(); |
69 } | 56 } |
70 } | 57 } |
71 | 58 |
72 if (merged.length == 1) return merged.single; | 59 // If our ranges have allowed all of their ranges, we'll have consumed all |
73 return new VersionUnion._(merged); | 60 // of them. |
74 } | 61 return theirRanges.current == null; |
75 | |
76 VersionUnion._(this.constraints); | |
77 | |
78 bool allows(Version version) => | |
79 constraints.any((constraint) => constraint.allows(version)); | |
80 | |
81 bool allowsAll(VersionConstraint other) { | |
82 var ourConstraints = constraints.iterator; | |
83 var theirConstraints = _constraintsFor(other).iterator; | |
84 | |
85 // Because both lists of constraints are ordered by minimum version, we can | |
86 // safely move through them linearly here. | |
87 ourConstraints.moveNext(); | |
88 theirConstraints.moveNext(); | |
89 while (ourConstraints.current != null && theirConstraints.current != null) { | |
90 if (ourConstraints.current.allowsAll(theirConstraints.current)) { | |
91 theirConstraints.moveNext(); | |
92 } else { | |
93 ourConstraints.moveNext(); | |
94 } | |
95 } | |
96 | |
97 // If our constraints have allowed all of their constraints, we'll have | |
98 // consumed all of them. | |
99 return theirConstraints.current == null; | |
100 } | 62 } |
101 | 63 |
102 bool allowsAny(VersionConstraint other) { | 64 bool allowsAny(VersionConstraint other) { |
103 var ourConstraints = constraints.iterator; | 65 var ourRanges = ranges.iterator; |
104 var theirConstraints = _constraintsFor(other).iterator; | 66 var theirRanges = _rangesFor(other).iterator; |
105 | 67 |
106 // Because both lists of constraints are ordered by minimum version, we can | 68 // Because both lists of ranges are ordered by minimum version, we can |
107 // safely move through them linearly here. | 69 // safely move through them linearly here. |
108 ourConstraints.moveNext(); | 70 ourRanges.moveNext(); |
109 theirConstraints.moveNext(); | 71 theirRanges.moveNext(); |
110 while (ourConstraints.current != null && theirConstraints.current != null) { | 72 while (ourRanges.current != null && theirRanges.current != null) { |
111 if (ourConstraints.current.allowsAny(theirConstraints.current)) { | 73 if (ourRanges.current.allowsAny(theirRanges.current)) { |
112 return true; | 74 return true; |
113 } | 75 } |
114 | 76 |
115 // Move the constraint with the higher max value forward. This ensures | 77 // Move the constraint with the higher max value forward. This ensures |
116 // that we keep both lists in sync as much as possible. | 78 // that we keep both lists in sync as much as possible. |
117 if (compareMax(ourConstraints.current, theirConstraints.current) < 0) { | 79 if (compareMax(ourRanges.current, theirRanges.current) < 0) { |
118 ourConstraints.moveNext(); | 80 ourRanges.moveNext(); |
119 } else { | 81 } else { |
120 theirConstraints.moveNext(); | 82 theirRanges.moveNext(); |
121 } | 83 } |
122 } | 84 } |
123 | 85 |
124 return false; | 86 return false; |
125 } | 87 } |
126 | 88 |
127 VersionConstraint intersect(VersionConstraint other) { | 89 VersionConstraint intersect(VersionConstraint other) { |
128 var ourConstraints = constraints.iterator; | 90 var ourRanges = ranges.iterator; |
129 var theirConstraints = _constraintsFor(other).iterator; | 91 var theirRanges = _rangesFor(other).iterator; |
130 | 92 |
131 // Because both lists of constraints are ordered by minimum version, we can | 93 // Because both lists of ranges are ordered by minimum version, we can |
132 // safely move through them linearly here. | 94 // safely move through them linearly here. |
133 var newConstraints = <VersionRange>[]; | 95 var newRanges = <VersionRange>[]; |
134 ourConstraints.moveNext(); | 96 ourRanges.moveNext(); |
135 theirConstraints.moveNext(); | 97 theirRanges.moveNext(); |
136 while (ourConstraints.current != null && theirConstraints.current != null) { | 98 while (ourRanges.current != null && theirRanges.current != null) { |
137 var intersection = ourConstraints.current | 99 var intersection = ourRanges.current |
138 .intersect(theirConstraints.current); | 100 .intersect(theirRanges.current); |
139 | 101 |
140 if (!intersection.isEmpty) newConstraints.add(intersection); | 102 if (!intersection.isEmpty) newRanges.add(intersection); |
141 | 103 |
142 // Move the constraint with the higher max value forward. This ensures | 104 // Move the constraint with the higher max value forward. This ensures |
143 // that we keep both lists in sync as much as possible, and that large | 105 // that we keep both lists in sync as much as possible, and that large |
144 // constraints have a chance to match multiple small constraints that they | 106 // ranges have a chance to match multiple small ranges that they contain. |
145 // contain. | 107 if (compareMax(ourRanges.current, theirRanges.current) < 0) { |
146 if (compareMax(ourConstraints.current, theirConstraints.current) < 0) { | 108 ourRanges.moveNext(); |
147 ourConstraints.moveNext(); | |
148 } else { | 109 } else { |
149 theirConstraints.moveNext(); | 110 theirRanges.moveNext(); |
150 } | 111 } |
151 } | 112 } |
152 | 113 |
153 if (newConstraints.isEmpty) return VersionConstraint.empty; | 114 if (newRanges.isEmpty) return VersionConstraint.empty; |
154 if (newConstraints.length == 1) return newConstraints.single; | 115 if (newRanges.length == 1) return newRanges.single; |
155 | 116 |
156 return new VersionUnion._(newConstraints); | 117 return new VersionUnion.fromRanges(newRanges); |
157 } | 118 } |
158 | 119 |
159 /// Returns [constraint] as a list of constraints. | 120 /// Returns [constraint] as a list of ranges. |
160 /// | 121 /// |
161 /// This is used to normalize constraints of various types. | 122 /// This is used to normalize ranges of various types. |
162 List<VersionRange> _constraintsFor(VersionConstraint constraint) { | 123 List<VersionRange> _rangesFor(VersionConstraint constraint) { |
163 if (constraint.isEmpty) return []; | 124 if (constraint.isEmpty) return []; |
164 if (constraint is VersionUnion) return constraint.constraints; | 125 if (constraint is VersionUnion) return constraint.ranges; |
165 if (constraint is VersionRange) return [constraint]; | 126 if (constraint is VersionRange) return [constraint]; |
166 throw new ArgumentError('Unknown VersionConstraint type $constraint.'); | 127 throw new ArgumentError('Unknown VersionConstraint type $constraint.'); |
167 } | 128 } |
168 | 129 |
169 VersionConstraint union(VersionConstraint other) => | 130 VersionConstraint union(VersionConstraint other) => |
170 new VersionConstraint.unionOf([this, other]); | 131 new VersionConstraint.unionOf([this, other]); |
171 | 132 |
172 bool operator ==(other) { | 133 bool operator ==(other) { |
173 if (other is! VersionUnion) return false; | 134 if (other is! VersionUnion) return false; |
174 return const ListEquality().equals(constraints, other.constraints); | 135 return const ListEquality().equals(ranges, other.ranges); |
175 } | 136 } |
176 | 137 |
177 int get hashCode => const ListEquality().hash(constraints); | 138 int get hashCode => const ListEquality().hash(ranges); |
178 | 139 |
179 String toString() => constraints.join(" or "); | 140 String toString() => ranges.join(" or "); |
180 } | 141 } |
OLD | NEW |