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.Diagnostics;
6 
7 namespace System.Runtime.Versioning
8 {
9     [Serializable]
10     public sealed class FrameworkName : IEquatable<FrameworkName>
11     {
12         private readonly string _identifier;
13         private readonly Version _version;
14         private readonly string _profile;
15         private string _fullName;
16 
17         private const char ComponentSeparator = ',';
18         private const char KeyValueSeparator = '=';
19         private const char VersionValuePrefix = 'v';
20         private const string VersionKey = "Version";
21         private const string ProfileKey = "Profile";
22 
23         private static readonly char[] s_componentSplitSeparator = { ComponentSeparator };
24 
25         public string Identifier
26         {
27             get
28             {
29                 Debug.Assert(_identifier != null);
30                 return _identifier;
31             }
32         }
33 
34         public Version Version
35         {
36             get
37             {
38                 Debug.Assert(_version != null);
39                 return _version;
40             }
41         }
42 
43         public string Profile
44         {
45             get
46             {
47                 Debug.Assert(_profile != null);
48                 return _profile;
49             }
50         }
51 
52         public string FullName
53         {
54             get
55             {
56                 if (_fullName == null)
57                 {
58                     if (string.IsNullOrEmpty(Profile))
59                     {
60                         _fullName =
61                             Identifier +
62                             ComponentSeparator + VersionKey + KeyValueSeparator + VersionValuePrefix +
63                             Version.ToString();
64                     }
65                     else
66                     {
67                         _fullName =
68                             Identifier +
69                             ComponentSeparator + VersionKey + KeyValueSeparator + VersionValuePrefix +
70                             Version.ToString() +
71                             ComponentSeparator + ProfileKey + KeyValueSeparator +
72                             Profile;
73                     }
74                 }
75                 Debug.Assert(_fullName != null);
76                 return _fullName;
77             }
78         }
79 
Equals(object obj)80         public override bool Equals(object obj)
81         {
82             return Equals(obj as FrameworkName);
83         }
84 
Equals(FrameworkName other)85         public bool Equals(FrameworkName other)
86         {
87             if (object.ReferenceEquals(other, null))
88             {
89                 return false;
90             }
91 
92             return Identifier == other.Identifier &&
93                 Version == other.Version &&
94                 Profile == other.Profile;
95         }
96 
GetHashCode()97         public override int GetHashCode()
98         {
99             return Identifier.GetHashCode() ^ Version.GetHashCode() ^ Profile.GetHashCode();
100         }
101 
ToString()102         public override string ToString()
103         {
104             return FullName;
105         }
106 
FrameworkName(string identifier, Version version)107         public FrameworkName(string identifier, Version version)
108             : this(identifier, version, null)
109         {
110         }
111 
FrameworkName(string identifier, Version version, string profile)112         public FrameworkName(string identifier, Version version, string profile)
113         {
114             if (identifier == null)
115             {
116                 throw new ArgumentNullException(nameof(identifier));
117             }
118 
119             identifier = identifier.Trim();
120             if (identifier.Length == 0)
121             {
122                 throw new ArgumentException(SR.Format(SR.net_emptystringcall, nameof(identifier)), nameof(identifier));
123             }
124             if (version == null)
125             {
126                 throw new ArgumentNullException(nameof(version));
127             }
128 
129             _identifier = identifier;
130             _version = version;
131             _profile = (profile == null) ? string.Empty : profile.Trim();
132         }
133 
134         // Parses strings in the following format: "<identifier>, Version=[v|V]<version>, Profile=<profile>"
135         //  - The identifier and version is required, profile is optional
136         //  - Only three components are allowed.
137         //  - The version string must be in the System.Version format; an optional "v" or "V" prefix is allowed
FrameworkName(string frameworkName)138         public FrameworkName(string frameworkName)
139         {
140             if (frameworkName == null)
141             {
142                 throw new ArgumentNullException(nameof(frameworkName));
143             }
144             if (frameworkName.Length == 0)
145             {
146                 throw new ArgumentException(SR.Format(SR.net_emptystringcall, nameof(frameworkName)), nameof(frameworkName));
147             }
148 
149             string[] components = frameworkName.Split(s_componentSplitSeparator);
150 
151             // Identifier and Version are required, Profile is optional.
152             if (components.Length < 2 || components.Length > 3)
153             {
154                 throw new ArgumentException(SR.Argument_FrameworkNameTooShort, nameof(frameworkName));
155             }
156 
157             //
158             // 1) Parse the "Identifier", which must come first. Trim any whitespace
159             //
160             _identifier = components[0].Trim();
161 
162             if (_identifier.Length == 0)
163             {
164                 throw new ArgumentException(SR.Argument_FrameworkNameInvalid, nameof(frameworkName));
165             }
166 
167             bool versionFound = false;
168             _profile = string.Empty;
169 
170             //
171             // The required "Version" and optional "Profile" component can be in any order
172             //
173             for (int i = 1; i < components.Length; i++)
174             {
175                 // Get the key/value pair separated by '='
176                 string component = components[i];
177                 int separatorIndex = component.IndexOf(KeyValueSeparator);
178 
179                 if (separatorIndex == -1 || separatorIndex != component.LastIndexOf(KeyValueSeparator))
180                 {
181                     throw new ArgumentException(SR.Argument_FrameworkNameInvalid, nameof(frameworkName));
182                 }
183 
184                 // Get the key and value, trimming any whitespace
185                 string key = component.Substring(0, separatorIndex).Trim();
186                 string value = component.Substring(separatorIndex + 1).Trim();
187 
188                 //
189                 // 2) Parse the required "Version" key value
190                 //
191                 if (key.Equals(VersionKey, StringComparison.OrdinalIgnoreCase))
192                 {
193                     versionFound = true;
194 
195                     // Allow the version to include a 'v' or 'V' prefix...
196                     if (value.Length > 0 && (value[0] == VersionValuePrefix || value[0] == 'V'))
197                     {
198                         value = value.Substring(1);
199                     }
200                     try
201                     {
202                         _version = Version.Parse(value);
203                     }
204                     catch (Exception e)
205                     {
206                         throw new ArgumentException(SR.Argument_FrameworkNameInvalidVersion, nameof(frameworkName), e);
207                     }
208                 }
209                 //
210                 // 3) Parse the optional "Profile" key value
211                 //
212                 else if (key.Equals(ProfileKey, StringComparison.OrdinalIgnoreCase))
213                 {
214                     if (!string.IsNullOrEmpty(value))
215                     {
216                         _profile = value;
217                     }
218                 }
219                 else
220                 {
221                     throw new ArgumentException(SR.Argument_FrameworkNameInvalid, nameof(frameworkName));
222                 }
223             }
224 
225             if (!versionFound)
226             {
227                 throw new ArgumentException(SR.Argument_FrameworkNameMissingVersion, nameof(frameworkName));
228             }
229         }
230 
operator ==(FrameworkName left, FrameworkName right)231         public static bool operator ==(FrameworkName left, FrameworkName right)
232         {
233             if (object.ReferenceEquals(left, null))
234             {
235                 return object.ReferenceEquals(right, null);
236             }
237             return left.Equals(right);
238         }
239 
operator !=(FrameworkName left, FrameworkName right)240         public static bool operator !=(FrameworkName left, FrameworkName right)
241         {
242             return !(left == right);
243         }
244     }
245 }
246