AsReadOnlySpan -> AsSpan rename to fix build breaks
[platform/upstream/coreclr.git] / src / mscorlib / shared / System / Version.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Globalization;
6 using System.Diagnostics;
7 using System.Text;
8
9 namespace System
10 {
11     // A Version object contains four hierarchical numeric components: major, minor,
12     // build and revision.  Build and revision may be unspecified, which is represented 
13     // internally as a -1.  By definition, an unspecified component matches anything 
14     // (both unspecified and specified), and an unspecified component is "less than" any
15     // specified component.
16
17     [Serializable]
18     [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
19     public sealed class Version : ICloneable, IComparable, IComparable<Version>, IEquatable<Version>, ISpanFormattable
20     {
21         // AssemblyName depends on the order staying the same
22         private readonly int _Major; // Do not rename (binary serialization)
23         private readonly int _Minor; // Do not rename (binary serialization)
24         private readonly int _Build = -1; // Do not rename (binary serialization)
25         private readonly int _Revision = -1; // Do not rename (binary serialization)
26
27         public Version(int major, int minor, int build, int revision)
28         {
29             if (major < 0)
30                 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
31
32             if (minor < 0)
33                 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
34
35             if (build < 0)
36                 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
37
38             if (revision < 0)
39                 throw new ArgumentOutOfRangeException(nameof(revision), SR.ArgumentOutOfRange_Version);
40
41             _Major = major;
42             _Minor = minor;
43             _Build = build;
44             _Revision = revision;
45         }
46
47         public Version(int major, int minor, int build)
48         {
49             if (major < 0)
50                 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
51
52             if (minor < 0)
53                 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
54
55             if (build < 0)
56                 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
57
58
59             _Major = major;
60             _Minor = minor;
61             _Build = build;
62         }
63
64         public Version(int major, int minor)
65         {
66             if (major < 0)
67                 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
68
69             if (minor < 0)
70                 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
71
72             _Major = major;
73             _Minor = minor;
74         }
75
76         public Version(String version)
77         {
78             Version v = Version.Parse(version);
79             _Major = v.Major;
80             _Minor = v.Minor;
81             _Build = v.Build;
82             _Revision = v.Revision;
83         }
84
85         public Version()
86         {
87             _Major = 0;
88             _Minor = 0;
89         }
90
91         private Version(Version version)
92         {
93             Debug.Assert(version != null);
94
95             _Major = version._Major;
96             _Minor = version._Minor;
97             _Build = version._Build;
98             _Revision = version._Revision;
99         }
100
101         public object Clone()
102         {
103             return new Version(this);
104         }
105
106         // Properties for setting and getting version numbers
107         public int Major
108         {
109             get { return _Major; }
110         }
111
112         public int Minor
113         {
114             get { return _Minor; }
115         }
116
117         public int Build
118         {
119             get { return _Build; }
120         }
121
122         public int Revision
123         {
124             get { return _Revision; }
125         }
126
127         public short MajorRevision
128         {
129             get { return (short)(_Revision >> 16); }
130         }
131
132         public short MinorRevision
133         {
134             get { return (short)(_Revision & 0xFFFF); }
135         }
136
137         public int CompareTo(Object version)
138         {
139             if (version == null)
140             {
141                 return 1;
142             }
143
144             Version v = version as Version;
145             if (v == null)
146             {
147                 throw new ArgumentException(SR.Arg_MustBeVersion);
148             }
149
150             return CompareTo(v);
151         }
152
153         public int CompareTo(Version value)
154         {
155             return
156                 object.ReferenceEquals(value, this) ? 0 :
157                 object.ReferenceEquals(value, null) ? 1 :
158                 _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
159                 _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
160                 _Build != value._Build ? (_Build > value._Build ? 1 : -1) :
161                 _Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
162                 0;
163         }
164
165         public override bool Equals(Object obj)
166         {
167             return Equals(obj as Version);
168         }
169
170         public bool Equals(Version obj)
171         {
172             return object.ReferenceEquals(obj, this) ||
173                 (!object.ReferenceEquals(obj, null) &&
174                 _Major == obj._Major &&
175                 _Minor == obj._Minor &&
176                 _Build == obj._Build &&
177                 _Revision == obj._Revision);
178         }
179
180         public override int GetHashCode()
181         {
182             // Let's assume that most version numbers will be pretty small and just
183             // OR some lower order bits together.
184
185             int accumulator = 0;
186
187             accumulator |= (_Major & 0x0000000F) << 28;
188             accumulator |= (_Minor & 0x000000FF) << 20;
189             accumulator |= (_Build & 0x000000FF) << 12;
190             accumulator |= (_Revision & 0x00000FFF);
191
192             return accumulator;
193         }
194
195         public override string ToString() =>
196             ToString(DefaultFormatFieldCount);
197
198         public string ToString(int fieldCount) =>
199             fieldCount == 0 ? string.Empty :
200             fieldCount == 1 ? _Major.ToString() :
201             StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount));
202
203         public bool TryFormat(Span<char> destination, out int charsWritten) =>
204             TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
205
206         public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
207         {
208             if (fieldCount == 0)
209             {
210                 charsWritten = 0;
211                 return true;
212             }
213             else if (fieldCount == 1)
214             {
215                 return _Major.TryFormat(destination, out charsWritten);
216             }
217
218             StringBuilder sb = ToCachedStringBuilder(fieldCount);
219             if (sb.Length <= destination.Length)
220             {
221                 sb.CopyTo(0, destination, sb.Length);
222                 StringBuilderCache.Release(sb);
223                 charsWritten = sb.Length;
224                 return true;
225             }
226
227             StringBuilderCache.Release(sb);
228             charsWritten = 0;
229             return false;
230         }
231
232         bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
233         {
234             // format and provider are ignored.
235             return TryFormat(destination, out charsWritten);
236         }
237
238         private int DefaultFormatFieldCount =>
239             _Build == -1 ? 2 :
240             _Revision == -1 ? 3 :
241             4;
242
243         private StringBuilder ToCachedStringBuilder(int fieldCount)
244         {
245             // Note: As we always have positive numbers then it is safe to convert the number to string
246             // regardless of the current culture as we'll not have any punctuation marks in the number.
247
248             if (fieldCount == 2)
249             {
250                 StringBuilder sb = StringBuilderCache.Acquire();
251                 sb.Append(_Major);
252                 sb.Append('.');
253                 sb.Append(_Minor);
254                 return sb;
255             }
256             else
257             {
258                 if (_Build == -1)
259                 {
260                     throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount));
261                 }
262
263                 if (fieldCount == 3)
264                 {
265                     StringBuilder sb = StringBuilderCache.Acquire();
266                     sb.Append(_Major);
267                     sb.Append('.');
268                     sb.Append(_Minor);
269                     sb.Append('.');
270                     sb.Append(_Build);
271                     return sb;
272                 }
273
274                 if (_Revision == -1)
275                 {
276                     throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount));
277                 }
278
279                 if (fieldCount == 4)
280                 {
281                     StringBuilder sb = StringBuilderCache.Acquire();
282                     sb.Append(_Major);
283                     sb.Append('.');
284                     sb.Append(_Minor);
285                     sb.Append('.');
286                     sb.Append(_Build);
287                     sb.Append('.');
288                     sb.Append(_Revision);
289                     return sb;
290                 }
291
292                 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount));
293             }
294         }
295
296         public static Version Parse(string input)
297         {
298             if (input == null)
299             {
300                 throw new ArgumentNullException(nameof(input));
301             }
302
303             return ParseVersion(input.AsSpan(), throwOnFailure: true);
304         }
305
306         public static Version Parse(ReadOnlySpan<char> input) =>
307             ParseVersion(input, throwOnFailure: true);
308
309         public static bool TryParse(string input, out Version result)
310         {
311             if (input == null)
312             {
313                 result = null;
314                 return false;
315             }
316
317             return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
318         }
319
320         public static bool TryParse(ReadOnlySpan<char> input, out Version result) =>
321             (result = ParseVersion(input, throwOnFailure: false)) != null;
322
323         private static Version ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
324         {
325             // Find the separator between major and minor.  It must exist.
326             int majorEnd = input.IndexOf('.');
327             if (majorEnd < 0)
328             {
329                 if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
330                 return null;
331             }
332
333             // Find the ends of the optional minor and build portions.
334             // We musn't have any separators after build.
335             int buildEnd = -1;
336             int minorEnd = input.IndexOf('.', majorEnd + 1);
337             if (minorEnd != -1)
338             {
339                 buildEnd = input.IndexOf('.', minorEnd + 1);
340                 if (buildEnd != -1)
341                 {
342                     if (input.IndexOf('.', buildEnd + 1) != -1)
343                     {
344                         if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
345                         return null;
346                     }
347                 }
348             }
349
350             int major, minor, build, revision;
351
352             // Parse the major version
353             if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out major))
354             {
355                 return null;
356             }
357
358             if (minorEnd != -1)
359             {
360                 // If there's more than a major and minor, parse the minor, too.
361                 if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
362                 {
363                     return null;
364                 }
365
366                 if (buildEnd != -1)
367                 {
368                     // major.minor.build.revision
369                     return
370                         TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
371                         TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
372                             new Version(major, minor, build, revision) :
373                             null;
374                 }
375                 else
376                 {
377                     // major.minor.build
378                     return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
379                         new Version(major, minor, build) :
380                         null;
381                 }
382             }
383             else
384             {
385                 // major.minor
386                 return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
387                     new Version(major, minor) :
388                     null;
389             }
390         }
391
392         private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
393         {
394             if (throwOnFailure)
395             {
396                 if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
397                 {
398                     throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version);
399                 }
400                 return true;
401             }
402
403             return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
404         }
405
406         public static bool operator ==(Version v1, Version v2)
407         {
408             if (Object.ReferenceEquals(v1, null))
409             {
410                 return Object.ReferenceEquals(v2, null);
411             }
412
413             return v1.Equals(v2);
414         }
415
416         public static bool operator !=(Version v1, Version v2)
417         {
418             return !(v1 == v2);
419         }
420
421         public static bool operator <(Version v1, Version v2)
422         {
423             if ((Object)v1 == null)
424                 throw new ArgumentNullException(nameof(v1));
425             return (v1.CompareTo(v2) < 0);
426         }
427
428         public static bool operator <=(Version v1, Version v2)
429         {
430             if ((Object)v1 == null)
431                 throw new ArgumentNullException(nameof(v1));
432             return (v1.CompareTo(v2) <= 0);
433         }
434
435         public static bool operator >(Version v1, Version v2)
436         {
437             return (v2 < v1);
438         }
439
440         public static bool operator >=(Version v1, Version v2)
441         {
442             return (v2 <= v1);
443         }
444     }
445 }