PatternParser.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #region Apache License
  2. //
  3. // Licensed to the Apache Software Foundation (ASF) under one or more
  4. // contributor license agreements. See the NOTICE file distributed with
  5. // this work for additional information regarding copyright ownership.
  6. // The ASF licenses this file to you under the Apache License, Version 2.0
  7. // (the "License"); you may not use this file except in compliance with
  8. // the License. You may obtain a copy of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS,
  14. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. // See the License for the specific language governing permissions and
  16. // limitations under the License.
  17. //
  18. #endregion
  19. using System;
  20. using System.Collections;
  21. using System.Globalization;
  22. using log4net.Core;
  23. using log4net.Layout;
  24. namespace log4net.Util
  25. {
  26. /// <summary>
  27. /// Most of the work of the <see cref="PatternLayout"/> class
  28. /// is delegated to the PatternParser class.
  29. /// </summary>
  30. /// <remarks>
  31. /// <para>
  32. /// The <c>PatternParser</c> processes a pattern string and
  33. /// returns a chain of <see cref="PatternConverter"/> objects.
  34. /// </para>
  35. /// </remarks>
  36. /// <author>Nicko Cadell</author>
  37. /// <author>Gert Driesen</author>
  38. public sealed class PatternParser
  39. {
  40. #region Public Instance Constructors
  41. /// <summary>
  42. /// Constructor
  43. /// </summary>
  44. /// <param name="pattern">The pattern to parse.</param>
  45. /// <remarks>
  46. /// <para>
  47. /// Initializes a new instance of the <see cref="PatternParser" /> class
  48. /// with the specified pattern string.
  49. /// </para>
  50. /// </remarks>
  51. public PatternParser(string pattern)
  52. {
  53. m_pattern = pattern;
  54. }
  55. #endregion Public Instance Constructors
  56. #region Public Instance Methods
  57. /// <summary>
  58. /// Parses the pattern into a chain of pattern converters.
  59. /// </summary>
  60. /// <returns>The head of a chain of pattern converters.</returns>
  61. /// <remarks>
  62. /// <para>
  63. /// Parses the pattern into a chain of pattern converters.
  64. /// </para>
  65. /// </remarks>
  66. public PatternConverter Parse()
  67. {
  68. string[] converterNamesCache = BuildCache();
  69. ParseInternal(m_pattern, converterNamesCache);
  70. return m_head;
  71. }
  72. #endregion Public Instance Methods
  73. #region Public Instance Properties
  74. /// <summary>
  75. /// Get the converter registry used by this parser
  76. /// </summary>
  77. /// <value>
  78. /// The converter registry used by this parser
  79. /// </value>
  80. /// <remarks>
  81. /// <para>
  82. /// Get the converter registry used by this parser
  83. /// </para>
  84. /// </remarks>
  85. public Hashtable PatternConverters
  86. {
  87. get { return m_patternConverters; }
  88. }
  89. #endregion Public Instance Properties
  90. #region Private Instance Methods
  91. /// <summary>
  92. /// Build the unified cache of converters from the static and instance maps
  93. /// </summary>
  94. /// <returns>the list of all the converter names</returns>
  95. /// <remarks>
  96. /// <para>
  97. /// Build the unified cache of converters from the static and instance maps
  98. /// </para>
  99. /// </remarks>
  100. private string[] BuildCache()
  101. {
  102. string[] converterNamesCache = new string[m_patternConverters.Keys.Count];
  103. m_patternConverters.Keys.CopyTo(converterNamesCache, 0);
  104. // sort array so that longer strings come first
  105. Array.Sort(converterNamesCache, 0, converterNamesCache.Length, StringLengthComparer.Instance);
  106. return converterNamesCache;
  107. }
  108. #region StringLengthComparer
  109. /// <summary>
  110. /// Sort strings by length
  111. /// </summary>
  112. /// <remarks>
  113. /// <para>
  114. /// <see cref="IComparer" /> that orders strings by string length.
  115. /// The longest strings are placed first
  116. /// </para>
  117. /// </remarks>
  118. private sealed class StringLengthComparer : IComparer
  119. {
  120. public static readonly StringLengthComparer Instance = new StringLengthComparer();
  121. private StringLengthComparer()
  122. {
  123. }
  124. #region Implementation of IComparer
  125. public int Compare(object x, object y)
  126. {
  127. string s1 = x as string;
  128. string s2 = y as string;
  129. if (s1 == null && s2 == null)
  130. {
  131. return 0;
  132. }
  133. if (s1 == null)
  134. {
  135. return 1;
  136. }
  137. if (s2 == null)
  138. {
  139. return -1;
  140. }
  141. return s2.Length.CompareTo(s1.Length);
  142. }
  143. #endregion
  144. }
  145. #endregion // StringLengthComparer
  146. /// <summary>
  147. /// Internal method to parse the specified pattern to find specified matches
  148. /// </summary>
  149. /// <param name="pattern">the pattern to parse</param>
  150. /// <param name="matches">the converter names to match in the pattern</param>
  151. /// <remarks>
  152. /// <para>
  153. /// The matches param must be sorted such that longer strings come before shorter ones.
  154. /// </para>
  155. /// </remarks>
  156. private void ParseInternal(string pattern, string[] matches)
  157. {
  158. int offset = 0;
  159. while(offset < pattern.Length)
  160. {
  161. int i = pattern.IndexOf('%', offset);
  162. if (i < 0 || i == pattern.Length - 1)
  163. {
  164. ProcessLiteral(pattern.Substring(offset));
  165. offset = pattern.Length;
  166. }
  167. else
  168. {
  169. if (pattern[i+1] == '%')
  170. {
  171. // Escaped
  172. ProcessLiteral(pattern.Substring(offset, i - offset + 1));
  173. offset = i + 2;
  174. }
  175. else
  176. {
  177. ProcessLiteral(pattern.Substring(offset, i - offset));
  178. offset = i + 1;
  179. FormattingInfo formattingInfo = new FormattingInfo();
  180. // Process formatting options
  181. // Look for the align flag
  182. if (offset < pattern.Length)
  183. {
  184. if (pattern[offset] == '-')
  185. {
  186. // Seen align flag
  187. formattingInfo.LeftAlign = true;
  188. offset++;
  189. }
  190. }
  191. // Look for the minimum length
  192. while (offset < pattern.Length && char.IsDigit(pattern[offset]))
  193. {
  194. // Seen digit
  195. if (formattingInfo.Min < 0)
  196. {
  197. formattingInfo.Min = 0;
  198. }
  199. formattingInfo.Min = (formattingInfo.Min * 10) + int.Parse(pattern[offset].ToString(), NumberFormatInfo.InvariantInfo);
  200. offset++;
  201. }
  202. // Look for the separator between min and max
  203. if (offset < pattern.Length)
  204. {
  205. if (pattern[offset] == '.')
  206. {
  207. // Seen separator
  208. offset++;
  209. }
  210. }
  211. // Look for the maximum length
  212. while (offset < pattern.Length && char.IsDigit(pattern[offset]))
  213. {
  214. // Seen digit
  215. if (formattingInfo.Max == int.MaxValue)
  216. {
  217. formattingInfo.Max = 0;
  218. }
  219. formattingInfo.Max = (formattingInfo.Max * 10) + int.Parse(pattern[offset].ToString(), NumberFormatInfo.InvariantInfo);
  220. offset++;
  221. }
  222. int remainingStringLength = pattern.Length - offset;
  223. // Look for pattern
  224. for(int m=0; m<matches.Length; m++)
  225. {
  226. string key = matches[m];
  227. if (key.Length <= remainingStringLength)
  228. {
  229. if (string.Compare(pattern, offset, key, 0, key.Length) == 0)
  230. {
  231. // Found match
  232. offset = offset + matches[m].Length;
  233. string option = null;
  234. // Look for option
  235. if (offset < pattern.Length)
  236. {
  237. if (pattern[offset] == '{')
  238. {
  239. // Seen option start
  240. offset++;
  241. int optEnd = pattern.IndexOf('}', offset);
  242. if (optEnd < 0)
  243. {
  244. // error
  245. }
  246. else
  247. {
  248. option = pattern.Substring(offset, optEnd - offset);
  249. offset = optEnd + 1;
  250. }
  251. }
  252. }
  253. ProcessConverter(matches[m], option, formattingInfo);
  254. break;
  255. }
  256. }
  257. }
  258. }
  259. }
  260. }
  261. }
  262. /// <summary>
  263. /// Process a parsed literal
  264. /// </summary>
  265. /// <param name="text">the literal text</param>
  266. private void ProcessLiteral(string text)
  267. {
  268. if (text.Length > 0)
  269. {
  270. // Convert into a pattern
  271. ProcessConverter("literal", text, new FormattingInfo());
  272. }
  273. }
  274. /// <summary>
  275. /// Process a parsed converter pattern
  276. /// </summary>
  277. /// <param name="converterName">the name of the converter</param>
  278. /// <param name="option">the optional option for the converter</param>
  279. /// <param name="formattingInfo">the formatting info for the converter</param>
  280. private void ProcessConverter(string converterName, string option, FormattingInfo formattingInfo)
  281. {
  282. LogLog.Debug(declaringType, "Converter ["+converterName+"] Option ["+option+"] Format [min="+formattingInfo.Min+",max="+formattingInfo.Max+",leftAlign="+formattingInfo.LeftAlign+"]");
  283. // Lookup the converter type
  284. ConverterInfo converterInfo = (ConverterInfo)m_patternConverters[converterName];
  285. if (converterInfo == null)
  286. {
  287. LogLog.Error(declaringType, "Unknown converter name ["+converterName+"] in conversion pattern.");
  288. }
  289. else
  290. {
  291. // Create the pattern converter
  292. PatternConverter pc = null;
  293. try
  294. {
  295. pc = (PatternConverter)Activator.CreateInstance(converterInfo.Type);
  296. }
  297. catch(Exception createInstanceEx)
  298. {
  299. LogLog.Error(declaringType, "Failed to create instance of Type [" + converterInfo.Type.FullName + "] using default constructor. Exception: " + createInstanceEx.ToString());
  300. }
  301. // formattingInfo variable is an instance variable, occasionally reset
  302. // and used over and over again
  303. pc.FormattingInfo = formattingInfo;
  304. pc.Option = option;
  305. pc.Properties = converterInfo.Properties;
  306. IOptionHandler optionHandler = pc as IOptionHandler;
  307. if (optionHandler != null)
  308. {
  309. optionHandler.ActivateOptions();
  310. }
  311. AddConverter(pc);
  312. }
  313. }
  314. /// <summary>
  315. /// Resets the internal state of the parser and adds the specified pattern converter
  316. /// to the chain.
  317. /// </summary>
  318. /// <param name="pc">The pattern converter to add.</param>
  319. private void AddConverter(PatternConverter pc)
  320. {
  321. // Add the pattern converter to the list.
  322. if (m_head == null)
  323. {
  324. m_head = m_tail = pc;
  325. }
  326. else
  327. {
  328. // Set the next converter on the tail
  329. // Update the tail reference
  330. // note that a converter may combine the 'next' into itself
  331. // and therefore the tail would not change!
  332. m_tail = m_tail.SetNext(pc);
  333. }
  334. }
  335. #endregion Protected Instance Methods
  336. #region Private Constants
  337. private const char ESCAPE_CHAR = '%';
  338. #endregion Private Constants
  339. #region Private Instance Fields
  340. /// <summary>
  341. /// The first pattern converter in the chain
  342. /// </summary>
  343. private PatternConverter m_head;
  344. /// <summary>
  345. /// the last pattern converter in the chain
  346. /// </summary>
  347. private PatternConverter m_tail;
  348. /// <summary>
  349. /// The pattern
  350. /// </summary>
  351. private string m_pattern;
  352. /// <summary>
  353. /// Internal map of converter identifiers to converter types
  354. /// </summary>
  355. /// <remarks>
  356. /// <para>
  357. /// This map overrides the static s_globalRulesRegistry map.
  358. /// </para>
  359. /// </remarks>
  360. private Hashtable m_patternConverters = new Hashtable();
  361. #endregion Private Instance Fields
  362. #region Private Static Fields
  363. /// <summary>
  364. /// The fully qualified type of the PatternParser class.
  365. /// </summary>
  366. /// <remarks>
  367. /// Used by the internal logger to record the Type of the
  368. /// log message.
  369. /// </remarks>
  370. private readonly static Type declaringType = typeof(PatternParser);
  371. #endregion Private Static Fields
  372. }
  373. }