UriTemplateCompoundPathSegment.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. //----------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //----------------------------------------------------------------
  4. namespace System
  5. {
  6. using System.Collections.Generic;
  7. using System.Collections.Specialized;
  8. using System.Runtime;
  9. using System.ServiceModel;
  10. using System.Text;
  11. // Thin wrapper around formatted string; use type system to help ensure we
  12. // are doing canonicalization right/consistently - the literal sections are held in an
  13. // un-escaped format
  14. // We are assuming that the string will be always built as Lit{Var}Lit[{Var}Lit[{Var}Lit[...]]],
  15. // when the first and last literals may be empty
  16. class UriTemplateCompoundPathSegment : UriTemplatePathSegment, IComparable<UriTemplateCompoundPathSegment>
  17. {
  18. readonly string firstLiteral;
  19. readonly List<VarAndLitPair> varLitPairs;
  20. CompoundSegmentClass csClass;
  21. UriTemplateCompoundPathSegment(string originalSegment, bool endsWithSlash, string firstLiteral)
  22. : base(originalSegment, UriTemplatePartType.Compound, endsWithSlash)
  23. {
  24. this.firstLiteral = firstLiteral;
  25. this.varLitPairs = new List<VarAndLitPair>();
  26. }
  27. public static new UriTemplateCompoundPathSegment CreateFromUriTemplate(string segment, UriTemplate template)
  28. {
  29. string origSegment = segment;
  30. bool endsWithSlash = segment.EndsWith("/", StringComparison.Ordinal);
  31. if (endsWithSlash)
  32. {
  33. segment = segment.Remove(segment.Length - 1);
  34. }
  35. int nextVarStart = segment.IndexOf("{", StringComparison.Ordinal);
  36. Fx.Assert(nextVarStart >= 0, "The method is only called after identifying a '{' character in the segment");
  37. string firstLiteral = ((nextVarStart > 0) ? segment.Substring(0, nextVarStart) : string.Empty);
  38. if (firstLiteral.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
  39. {
  40. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
  41. SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, template.originalTemplate, UriTemplate.WildcardPath)));
  42. }
  43. UriTemplateCompoundPathSegment result = new UriTemplateCompoundPathSegment(origSegment, endsWithSlash,
  44. ((firstLiteral != string.Empty) ? Uri.UnescapeDataString(firstLiteral) : string.Empty));
  45. do
  46. {
  47. int nextVarEnd = segment.IndexOf("}", nextVarStart + 1, StringComparison.Ordinal);
  48. if (nextVarEnd < nextVarStart + 2)
  49. {
  50. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
  51. SR.GetString(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
  52. }
  53. bool hasDefault;
  54. string varName = template.AddPathVariable(UriTemplatePartType.Compound,
  55. segment.Substring(nextVarStart + 1, nextVarEnd - nextVarStart - 1), out hasDefault);
  56. if (hasDefault)
  57. {
  58. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
  59. SR.GetString(SR.UTDefaultValueToCompoundSegmentVar, template, origSegment, varName)));
  60. }
  61. nextVarStart = segment.IndexOf("{", nextVarEnd + 1, StringComparison.Ordinal);
  62. string literal;
  63. if (nextVarStart > 0)
  64. {
  65. if (nextVarStart == nextVarEnd + 1)
  66. {
  67. throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("template",
  68. SR.GetString(SR.UTDoesNotSupportAdjacentVarsInCompoundSegment, template, segment));
  69. }
  70. literal = segment.Substring(nextVarEnd + 1, nextVarStart - nextVarEnd - 1);
  71. }
  72. else if (nextVarEnd + 1 < segment.Length)
  73. {
  74. literal = segment.Substring(nextVarEnd + 1);
  75. }
  76. else
  77. {
  78. literal = string.Empty;
  79. }
  80. if (literal.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
  81. {
  82. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
  83. SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, template.originalTemplate, UriTemplate.WildcardPath)));
  84. }
  85. if (literal.IndexOf('}') != -1)
  86. {
  87. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
  88. SR.GetString(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
  89. }
  90. result.varLitPairs.Add(new VarAndLitPair(varName, ((literal == string.Empty) ? string.Empty : Uri.UnescapeDataString(literal))));
  91. } while (nextVarStart > 0);
  92. if (string.IsNullOrEmpty(result.firstLiteral))
  93. {
  94. if (string.IsNullOrEmpty(result.varLitPairs[result.varLitPairs.Count - 1].Literal))
  95. {
  96. result.csClass = CompoundSegmentClass.HasNoPrefixNorSuffix;
  97. }
  98. else
  99. {
  100. result.csClass = CompoundSegmentClass.HasOnlySuffix;
  101. }
  102. }
  103. else
  104. {
  105. if (string.IsNullOrEmpty(result.varLitPairs[result.varLitPairs.Count - 1].Literal))
  106. {
  107. result.csClass = CompoundSegmentClass.HasOnlyPrefix;
  108. }
  109. else
  110. {
  111. result.csClass = CompoundSegmentClass.HasPrefixAndSuffix;
  112. }
  113. }
  114. return result;
  115. }
  116. public override void Bind(string[] values, ref int valueIndex, StringBuilder path)
  117. {
  118. Fx.Assert(valueIndex + this.varLitPairs.Count <= values.Length, "Not enough values to bind");
  119. path.Append(this.firstLiteral);
  120. for (int pairIndex = 0; pairIndex < this.varLitPairs.Count; pairIndex++)
  121. {
  122. path.Append(values[valueIndex++]);
  123. path.Append(this.varLitPairs[pairIndex].Literal);
  124. }
  125. if (this.EndsWithSlash)
  126. {
  127. path.Append("/");
  128. }
  129. }
  130. public override bool IsEquivalentTo(UriTemplatePathSegment other, bool ignoreTrailingSlash)
  131. {
  132. if (other == null)
  133. {
  134. Fx.Assert("why would we ever call this?");
  135. return false;
  136. }
  137. if (!ignoreTrailingSlash && (this.EndsWithSlash != other.EndsWithSlash))
  138. {
  139. return false;
  140. }
  141. UriTemplateCompoundPathSegment otherAsCompound = other as UriTemplateCompoundPathSegment;
  142. if (otherAsCompound == null)
  143. {
  144. // if other can't be cast as a compound then it can't be equivalent
  145. return false;
  146. }
  147. if (this.varLitPairs.Count != otherAsCompound.varLitPairs.Count)
  148. {
  149. return false;
  150. }
  151. if (StringComparer.OrdinalIgnoreCase.Compare(this.firstLiteral, otherAsCompound.firstLiteral) != 0)
  152. {
  153. return false;
  154. }
  155. for (int pairIndex = 0; pairIndex < this.varLitPairs.Count; pairIndex++)
  156. {
  157. if (StringComparer.OrdinalIgnoreCase.Compare(this.varLitPairs[pairIndex].Literal,
  158. otherAsCompound.varLitPairs[pairIndex].Literal) != 0)
  159. {
  160. return false;
  161. }
  162. }
  163. return true;
  164. }
  165. public override bool IsMatch(UriTemplateLiteralPathSegment segment, bool ignoreTrailingSlash)
  166. {
  167. if (!ignoreTrailingSlash && (this.EndsWithSlash != segment.EndsWithSlash))
  168. {
  169. return false;
  170. }
  171. return TryLookup(segment.AsUnescapedString(), null);
  172. }
  173. public override void Lookup(string segment, NameValueCollection boundParameters)
  174. {
  175. if (!TryLookup(segment, boundParameters))
  176. {
  177. Fx.Assert("How can that be? Lookup is expected to be called after IsMatch");
  178. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
  179. SR.GetString(SR.UTCSRLookupBeforeMatch)));
  180. }
  181. }
  182. bool TryLookup(string segment, NameValueCollection boundParameters)
  183. {
  184. int segmentPosition = 0;
  185. if (!string.IsNullOrEmpty(this.firstLiteral))
  186. {
  187. if (segment.StartsWith(this.firstLiteral, StringComparison.Ordinal))
  188. {
  189. segmentPosition = this.firstLiteral.Length;
  190. }
  191. else
  192. {
  193. return false;
  194. }
  195. }
  196. for (int pairIndex = 0; pairIndex < this.varLitPairs.Count - 1; pairIndex++)
  197. {
  198. int nextLiteralPosition = segment.IndexOf(this.varLitPairs[pairIndex].Literal, segmentPosition, StringComparison.Ordinal);
  199. if (nextLiteralPosition < segmentPosition + 1)
  200. {
  201. return false;
  202. }
  203. if (boundParameters != null)
  204. {
  205. string varValue = segment.Substring(segmentPosition, nextLiteralPosition - segmentPosition);
  206. boundParameters.Add(this.varLitPairs[pairIndex].VarName, varValue);
  207. }
  208. segmentPosition = nextLiteralPosition + this.varLitPairs[pairIndex].Literal.Length;
  209. }
  210. if (segmentPosition < segment.Length)
  211. {
  212. if (string.IsNullOrEmpty(this.varLitPairs[varLitPairs.Count - 1].Literal))
  213. {
  214. if (boundParameters != null)
  215. {
  216. boundParameters.Add(this.varLitPairs[varLitPairs.Count - 1].VarName,
  217. segment.Substring(segmentPosition));
  218. }
  219. return true;
  220. }
  221. else if ((segmentPosition + this.varLitPairs[varLitPairs.Count - 1].Literal.Length < segment.Length) &&
  222. segment.EndsWith(this.varLitPairs[varLitPairs.Count - 1].Literal, StringComparison.Ordinal))
  223. {
  224. if (boundParameters != null)
  225. {
  226. boundParameters.Add(this.varLitPairs[varLitPairs.Count - 1].VarName,
  227. segment.Substring(segmentPosition, segment.Length - segmentPosition - this.varLitPairs[varLitPairs.Count - 1].Literal.Length));
  228. }
  229. return true;
  230. }
  231. else
  232. {
  233. return false;
  234. }
  235. }
  236. else
  237. {
  238. return false;
  239. }
  240. }
  241. // A note about comparing compound segments:
  242. // We are using this for generating the sorted collections at the nodes of the UriTemplateTrieNode.
  243. // The idea is that we are sorting the segments based on preferred matching, when we have two
  244. // compound segments matching the same wire segment, we will give preference to the preceding one.
  245. // The order is based on the following concepts:
  246. // - We are defining four classes of compound segments: prefix+suffix, prefix-only, suffix-only
  247. // and none
  248. // - Whenever we are comparing segments from different class the preferred one is the segment with
  249. // the prefared class, based on the order we defined them (p+s \ p \ s \ n).
  250. // - Within each class the preference is based on the prefix\suffix, while prefix has precedence
  251. // over suffix if both exists.
  252. // - If after comparing the class, as well as the prefix\suffix, we didn't reach to a conclusion,
  253. // the preference is given to the segment with more variables parts.
  254. // This order mostly follows the intuitive common sense; the major issue comes from preferring the
  255. // prefix over the suffix in the case where both exist. This is derived from the problematic of any
  256. // other type of solution that don't prefere the prefix over the suffix or vice versa. To better
  257. // understanding lets considered the following example:
  258. // In comparing 'foo{x}bar' and 'food{x}ar', unless we are preferring prefix or suffix, we have
  259. // to state that they have the same order. So is the case with 'foo{x}babar' and 'food{x}ar', which
  260. // will lead us to claiming the 'foo{x}bar' and 'foo{x}babar' are from the same order, which they
  261. // clearly are not.
  262. // Taking other approaches to this problem results in similar cases. The only solution is preferring
  263. // either the prefix or the suffix over the other; since we already preferred prefix over suffix
  264. // implicitly (we preferred the prefix only class over the suffix only, we also prefared literal
  265. // over variable, if in the same path segment) that still maintain consistency.
  266. // Therefore:
  267. // - 'food{var}' should be before 'foo{var}'; '{x}.{y}.{z}' should be before '{x}.{y}'.
  268. // - the order between '{var}bar' and '{var}qux' is not important
  269. // - '{x}.{y}' and '{x}_{y}' should have the same order
  270. // - 'foo{x}bar' is less preferred than 'food{x}ar'
  271. // In the above third case - if we are opening the table with allowDuplicate=false, we will throw;
  272. // if we are opening it with allowDuplicate=true we will let it go and might match both templates
  273. // for certain wire candidates.
  274. int IComparable<UriTemplateCompoundPathSegment>.CompareTo(UriTemplateCompoundPathSegment other)
  275. {
  276. Fx.Assert(other != null, "We are only expected to get here for comparing real compound segments");
  277. switch (this.csClass)
  278. {
  279. case CompoundSegmentClass.HasPrefixAndSuffix:
  280. switch (other.csClass)
  281. {
  282. case CompoundSegmentClass.HasPrefixAndSuffix:
  283. return CompareToOtherThatHasPrefixAndSuffix(other);
  284. case CompoundSegmentClass.HasOnlyPrefix:
  285. case CompoundSegmentClass.HasOnlySuffix:
  286. case CompoundSegmentClass.HasNoPrefixNorSuffix:
  287. return -1;
  288. default:
  289. Fx.Assert("Invalid other.CompoundSegmentClass");
  290. return 0;
  291. }
  292. case CompoundSegmentClass.HasOnlyPrefix:
  293. switch (other.csClass)
  294. {
  295. case CompoundSegmentClass.HasPrefixAndSuffix:
  296. return 1;
  297. case CompoundSegmentClass.HasOnlyPrefix:
  298. return CompareToOtherThatHasOnlyPrefix(other);
  299. case CompoundSegmentClass.HasOnlySuffix:
  300. case CompoundSegmentClass.HasNoPrefixNorSuffix:
  301. return -1;
  302. default:
  303. Fx.Assert("Invalid other.CompoundSegmentClass");
  304. return 0;
  305. }
  306. case CompoundSegmentClass.HasOnlySuffix:
  307. switch (other.csClass)
  308. {
  309. case CompoundSegmentClass.HasPrefixAndSuffix:
  310. case CompoundSegmentClass.HasOnlyPrefix:
  311. return 1;
  312. case CompoundSegmentClass.HasOnlySuffix:
  313. return CompareToOtherThatHasOnlySuffix(other);
  314. case CompoundSegmentClass.HasNoPrefixNorSuffix:
  315. return -1;
  316. default:
  317. Fx.Assert("Invalid other.CompoundSegmentClass");
  318. return 0;
  319. }
  320. case CompoundSegmentClass.HasNoPrefixNorSuffix:
  321. switch (other.csClass)
  322. {
  323. case CompoundSegmentClass.HasPrefixAndSuffix:
  324. case CompoundSegmentClass.HasOnlyPrefix:
  325. case CompoundSegmentClass.HasOnlySuffix:
  326. return 1;
  327. case CompoundSegmentClass.HasNoPrefixNorSuffix:
  328. return CompareToOtherThatHasNoPrefixNorSuffix(other);
  329. default:
  330. Fx.Assert("Invalid other.CompoundSegmentClass");
  331. return 0;
  332. }
  333. default:
  334. Fx.Assert("Invalid this.CompoundSegmentClass");
  335. return 0;
  336. }
  337. }
  338. int CompareToOtherThatHasPrefixAndSuffix(UriTemplateCompoundPathSegment other)
  339. {
  340. Fx.Assert(this.csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
  341. Fx.Assert(other.csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
  342. // In this case we are determining the order based on the prefix of the two segments,
  343. // then by their suffix and then based on the number of variables
  344. int prefixOrder = ComparePrefixToOtherPrefix(other);
  345. if (prefixOrder == 0)
  346. {
  347. int suffixOrder = CompareSuffixToOtherSuffix(other);
  348. if (suffixOrder == 0)
  349. {
  350. return (other.varLitPairs.Count - this.varLitPairs.Count);
  351. }
  352. else
  353. {
  354. return suffixOrder;
  355. }
  356. }
  357. else
  358. {
  359. return prefixOrder;
  360. }
  361. }
  362. int CompareToOtherThatHasOnlyPrefix(UriTemplateCompoundPathSegment other)
  363. {
  364. Fx.Assert(this.csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
  365. Fx.Assert(other.csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
  366. // In this case we are determining the order based on the prefix of the two segments,
  367. // then based on the number of variables
  368. int prefixOrder = ComparePrefixToOtherPrefix(other);
  369. if (prefixOrder == 0)
  370. {
  371. return (other.varLitPairs.Count - this.varLitPairs.Count);
  372. }
  373. else
  374. {
  375. return prefixOrder;
  376. }
  377. }
  378. int CompareToOtherThatHasOnlySuffix(UriTemplateCompoundPathSegment other)
  379. {
  380. Fx.Assert(this.csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
  381. Fx.Assert(other.csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
  382. // In this case we are determining the order based on the suffix of the two segments,
  383. // then based on the number of variables
  384. int suffixOrder = CompareSuffixToOtherSuffix(other);
  385. if (suffixOrder == 0)
  386. {
  387. return (other.varLitPairs.Count - this.varLitPairs.Count);
  388. }
  389. else
  390. {
  391. return suffixOrder;
  392. }
  393. }
  394. int CompareToOtherThatHasNoPrefixNorSuffix(UriTemplateCompoundPathSegment other)
  395. {
  396. Fx.Assert(this.csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
  397. Fx.Assert(other.csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
  398. // In this case the order is determined by the number of variables
  399. return (other.varLitPairs.Count - this.varLitPairs.Count);
  400. }
  401. int ComparePrefixToOtherPrefix(UriTemplateCompoundPathSegment other)
  402. {
  403. return string.Compare(other.firstLiteral, this.firstLiteral, StringComparison.OrdinalIgnoreCase);
  404. }
  405. int CompareSuffixToOtherSuffix(UriTemplateCompoundPathSegment other)
  406. {
  407. string reversedSuffix = ReverseString(this.varLitPairs[this.varLitPairs.Count - 1].Literal);
  408. string reversedOtherSuffix = ReverseString(other.varLitPairs[other.varLitPairs.Count - 1].Literal);
  409. return string.Compare(reversedOtherSuffix, reversedSuffix, StringComparison.OrdinalIgnoreCase);
  410. }
  411. static string ReverseString(string stringToReverse)
  412. {
  413. char[] reversedString = new char[stringToReverse.Length];
  414. for (int i = 0; i < stringToReverse.Length; i++)
  415. {
  416. reversedString[i] = stringToReverse[stringToReverse.Length - i - 1];
  417. }
  418. return new string(reversedString);
  419. }
  420. enum CompoundSegmentClass
  421. {
  422. Undefined,
  423. HasPrefixAndSuffix,
  424. HasOnlyPrefix,
  425. HasOnlySuffix,
  426. HasNoPrefixNorSuffix
  427. }
  428. struct VarAndLitPair
  429. {
  430. readonly string literal;
  431. readonly string varName;
  432. public VarAndLitPair(string varName, string literal)
  433. {
  434. this.varName = varName;
  435. this.literal = literal;
  436. }
  437. public string Literal
  438. {
  439. get
  440. {
  441. return this.literal;
  442. }
  443. }
  444. public string VarName
  445. {
  446. get
  447. {
  448. return this.varName;
  449. }
  450. }
  451. }
  452. }
  453. }