PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/eBlog/eBlog.Template/Runtime/Parser/Node/ASTReference.cs

#
C# | 588 lines | 480 code | 41 blank | 67 comment | 35 complexity | 6b215cc9bba4c3596616afdebe26e879 MD5 | raw file
Possible License(s): LGPL-2.1
  1. namespace NVelocity.Runtime.Parser.Node
  2. {
  3. using System;
  4. using System.Collections;
  5. using System.IO;
  6. using System.Reflection;
  7. using System.Text;
  8. using Context;
  9. using Exception;
  10. using NVelocity.App.Events;
  11. using NVelocity.Exception;
  12. /// <summary>
  13. /// Reference types
  14. /// </summary>
  15. internal enum ReferenceType
  16. {
  17. Normal = 1,
  18. Formal = 2,
  19. Quiet = 3,
  20. Runt = 4,
  21. }
  22. /// <summary>
  23. /// This class is responsible for handling the references in
  24. /// VTL ($foo).
  25. ///
  26. /// Please look at the Parser.jjt file which is
  27. /// what controls the generation of this class.
  28. /// </summary>
  29. /// <author> <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a></author>
  30. /// <author> <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a></author>
  31. /// <author> <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a></author>
  32. /// <author> <a href="mailto:kjohnson@transparent.com">Kent Johnson</a></author>
  33. /// <version> $Id: ASTReference.cs,v 1.4 2003/10/27 13:54:10 corts Exp $ </version>
  34. public class ASTReference : SimpleNode
  35. {
  36. private ReferenceType referenceType;
  37. private String nullString;
  38. private String rootString;
  39. private bool escaped = false;
  40. private bool computableReference = true;
  41. private String escPrefix = string.Empty;
  42. private String morePrefix = string.Empty;
  43. private String identifier = string.Empty;
  44. private String literal = null;
  45. private Stack referenceStack;
  46. private int numChildren = 0;
  47. public ASTReference(int id) : base(id)
  48. {
  49. }
  50. public ASTReference(Parser p, int id) : base(p, id)
  51. {
  52. }
  53. /// <summary>
  54. /// Returns the 'root string', the reference key
  55. /// </summary>
  56. public String RootString
  57. {
  58. get { return rootString; }
  59. }
  60. public void SetLiteral(String value)
  61. {
  62. if (literal == null)
  63. {
  64. literal = value;
  65. }
  66. }
  67. public override String Literal
  68. {
  69. get
  70. {
  71. if (literal != null)
  72. {
  73. return literal;
  74. }
  75. return base.Literal;
  76. }
  77. }
  78. /// <summary>Accept the visitor.</summary>
  79. public override Object Accept(IParserVisitor visitor, Object data)
  80. {
  81. return visitor.Visit(this, data);
  82. }
  83. public override Object Init(IInternalContextAdapter context, Object data)
  84. {
  85. // init our children
  86. base.Init(context, data);
  87. // the only thing we can do in init() is getRoot()
  88. // as that is template based, not context based,
  89. // so it's thread- and context-safe
  90. rootString = Root;
  91. numChildren = ChildrenCount;
  92. // and if appropriate...
  93. if (numChildren > 0)
  94. {
  95. identifier = GetChild(numChildren - 1).FirstToken.Image;
  96. }
  97. return data;
  98. }
  99. /// <summary>
  100. /// gets an Object that 'is' the value of the reference
  101. /// </summary>
  102. public override Object Execute(Object o, IInternalContextAdapter context)
  103. {
  104. if (referenceType == ReferenceType.Runt)
  105. return null;
  106. // get the root object from the context
  107. Object result = GetVariableValue(context, rootString);
  108. if (context.EventCartridge != null)
  109. {
  110. referenceStack = new Stack();
  111. referenceStack.Push(result);
  112. }
  113. if (result == null)
  114. {
  115. return null;
  116. }
  117. // Iteratively work 'down' (it's flat...) the reference
  118. // to get the value, but check to make sure that
  119. // every result along the path is valid. For example:
  120. //
  121. // $hashtable.Customer.Name
  122. //
  123. // The $hashtable may be valid, but there is no key
  124. // 'Customer' in the hashtable so we want to stop
  125. // when we find a null value and return the null
  126. // so the error gets logged.
  127. try
  128. {
  129. for(int i = 0; i < numChildren; i++)
  130. {
  131. result = GetChild(i).Execute(result, context);
  132. if (referenceStack != null)
  133. {
  134. referenceStack.Push(result);
  135. }
  136. if (result == null)
  137. {
  138. return null;
  139. }
  140. }
  141. return result;
  142. }
  143. catch(MethodInvocationException methodInvocationException)
  144. {
  145. // someone tossed their cookies
  146. runtimeServices.Error(
  147. string.Format("Method {0} threw exception for reference ${1} in template {2} at [{3},{4}]",
  148. methodInvocationException.MethodName, rootString, context.CurrentTemplateName, Line, Column));
  149. methodInvocationException.ReferenceName = rootString;
  150. throw new Exception("模板" + context.CurrentTemplateName + "-行"+Line.ToString()+"列"+Column.ToString()+"的语法:" + methodInvocationException.Message);
  151. throw new ArgumentNullException();
  152. }
  153. }
  154. /// <summary>
  155. /// gets the value of the reference and outputs it to the
  156. /// writer.
  157. /// </summary>
  158. /// <param name="context"> context of data to use in getting value </param>
  159. /// <param name="writer"> writer to render to </param>
  160. public override bool Render(IInternalContextAdapter context, TextWriter writer)
  161. {
  162. if (referenceType == ReferenceType.Runt)
  163. {
  164. writer.Write(rootString);
  165. return true;
  166. }
  167. Object value = Execute(null, context);
  168. // if this reference is escaped (\$foo) then we want to do one of two things :
  169. // 1) if this is a reference in the context, then we want to print $foo
  170. // 2) if not, then \$foo (its considered shmoo, not VTL)
  171. if (escaped)
  172. {
  173. StringBuilder b = new StringBuilder();
  174. b.Append(escPrefix);
  175. if (value == null)
  176. {
  177. b.Append("\\");
  178. }
  179. b.Append(nullString);
  180. writer.Write(b);
  181. return true;
  182. }
  183. // the normal processing
  184. // if we have an event cartridge, get a new value object
  185. EventCartridge eventCartridge = context.EventCartridge;
  186. if (eventCartridge != null && referenceStack != null)
  187. {
  188. value = eventCartridge.ReferenceInsert(referenceStack, nullString, value);
  189. }
  190. // if value is null...
  191. if (value == null)
  192. {
  193. // write prefix twice, because it's shmoo, so the \ don't escape each other...
  194. StringBuilder b = new StringBuilder();
  195. b.Append(escPrefix);
  196. b.Append(escPrefix);
  197. b.Append(morePrefix);
  198. b.Append(nullString);
  199. writer.Write(b);
  200. if (referenceType != ReferenceType.Quiet &&
  201. runtimeServices.GetBoolean(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true))
  202. {
  203. runtimeServices.Warn(
  204. new ReferenceException(string.Format("reference : template = {0}", context.CurrentTemplateName), this));
  205. }
  206. return true;
  207. }
  208. else
  209. {
  210. // non-null processing
  211. StringBuilder b = new StringBuilder();
  212. b.Append(escPrefix);
  213. b.Append(morePrefix);
  214. b.Append(value);
  215. writer.Write(b);
  216. return true;
  217. }
  218. }
  219. /// <summary>
  220. /// Computes boolean value of this reference
  221. /// Returns the actual value of reference return type
  222. /// boolean, and 'true' if value is not null
  223. /// </summary>
  224. /// <param name="context">context to compute value with</param>
  225. public override bool Evaluate(IInternalContextAdapter context)
  226. {
  227. Object value = Execute(null, context);
  228. if (value == null)
  229. {
  230. return false;
  231. }
  232. else if (value is Boolean)
  233. {
  234. return (bool) value;
  235. }
  236. else
  237. {
  238. return true;
  239. }
  240. }
  241. public override Object Value(IInternalContextAdapter context)
  242. {
  243. return (computableReference ? Execute(null, context) : null);
  244. }
  245. /// <summary>
  246. /// Sets the value of a complex reference (something like $foo.bar)
  247. /// Currently used by ASTSetReference()
  248. /// </summary>
  249. /// <seealso cref=" ASTSetDirective"/>
  250. /// <param name="context">context object containing this reference</param>
  251. /// <param name="value">Object to set as value</param>
  252. /// <returns>true if successful, false otherwise</returns>
  253. public bool SetValue(IInternalContextAdapter context, Object value)
  254. {
  255. // The rootOfIntrospection is the object we will
  256. // retrieve from the Context. This is the base
  257. // object we will apply reflection to.
  258. Object result = GetVariableValue(context, rootString);
  259. if (result == null)
  260. {
  261. runtimeServices.Error(
  262. new ReferenceException(string.Format("reference set : template = {0}", context.CurrentTemplateName), this));
  263. return false;
  264. }
  265. // How many child nodes do we have?
  266. for(int i = 0; i < numChildren - 1; i++)
  267. {
  268. result = GetChild(i).Execute(result, context);
  269. if (result == null)
  270. {
  271. runtimeServices.Error(
  272. new ReferenceException(string.Format("reference set : template = {0}", context.CurrentTemplateName), this));
  273. return false;
  274. }
  275. }
  276. // We support two ways of setting the value in a #set($ref.foo = $value ) :
  277. // 1) ref.setFoo( value )
  278. // 2) ref,put("foo", value ) to parallel the get() map introspection
  279. try
  280. {
  281. IDuck duck = result as IDuck;
  282. if (duck == null)
  283. {
  284. // first, we introspect for the set<identifier> setter method
  285. Type c = result.GetType();
  286. PropertyInfo p;
  287. try
  288. {
  289. p = runtimeServices.Introspector.GetProperty(c, identifier);
  290. if (p == null)
  291. {
  292. throw new MethodAccessException();
  293. }
  294. }
  295. catch(MethodAccessException)
  296. {
  297. StringBuilder sb = new StringBuilder();
  298. sb.Append(identifier);
  299. if (Char.IsLower(sb[0]))
  300. {
  301. sb[0] = Char.ToUpper(sb[0]);
  302. }
  303. else
  304. {
  305. sb[0] = Char.ToLower(sb[0]);
  306. }
  307. p = runtimeServices.Introspector.GetProperty(c, sb.ToString());
  308. if (p == null)
  309. {
  310. throw;
  311. }
  312. }
  313. // and if we get here, getMethod() didn't chuck an exception...
  314. Object[] args = new Object[] {};
  315. p.SetValue(result, value, args);
  316. }
  317. else
  318. {
  319. duck.SetInvoke(identifier, value);
  320. }
  321. }
  322. catch(MethodAccessException)
  323. {
  324. // right now, we only support the IDictionary interface
  325. if (result is IDictionary)
  326. {
  327. try
  328. {
  329. IDictionary d = (IDictionary) result;
  330. d[identifier] = value;
  331. }
  332. catch(Exception ex)
  333. {
  334. runtimeServices.Error(
  335. string.Format("ASTReference Map.put : exception : {0} template = {1} [{2},{3}]", ex, context.CurrentTemplateName,
  336. Line, Column));
  337. return false;
  338. }
  339. }
  340. else
  341. {
  342. runtimeServices.Error(
  343. string.Format("ASTReference : cannot find {0} as settable property or key to Map in template = {1} [{2},{3}]",
  344. identifier, context.CurrentTemplateName, Line, Column));
  345. return false;
  346. }
  347. }
  348. catch(TargetInvocationException targetInvocationException)
  349. {
  350. // this is possible
  351. throw new MethodInvocationException(
  352. string.Format("ASTReference : Invocation of method '{0}' in {1} threw exception {2}", identifier, result.GetType(),
  353. targetInvocationException.GetBaseException().GetType()), targetInvocationException, identifier);
  354. }
  355. catch(Exception e)
  356. {
  357. // maybe a security exception?
  358. runtimeServices.Error(
  359. string.Format("ASTReference setValue() : exception : {0} template = {1} [{2},{3}]", e, context.CurrentTemplateName,
  360. Line, Column));
  361. return false;
  362. }
  363. return true;
  364. }
  365. public Object GetVariableValue(IContext context, String variable)
  366. {
  367. return context.Get(variable);
  368. }
  369. private String Root
  370. {
  371. get
  372. {
  373. Token t = FirstToken;
  374. // we have a special case where something like
  375. // $(\\)*!, where the user want's to see something
  376. // like $!blargh in the output, but the ! prevents it from showing.
  377. // I think that at this point, this isn't a reference.
  378. // so, see if we have "\\!"
  379. int slashBang = t.Image.IndexOf("\\!");
  380. if (slashBang != -1)
  381. {
  382. // lets do all the work here. I would argue that if this occurs, it's
  383. // not a reference at all, so preceeding \ characters in front of the $
  384. // are just schmoo. So we just do the escape processing trick (even | odd)
  385. // and move on. This kind of breaks the rule pattern of $ and # but '!' really
  386. // tosses a wrench into things.
  387. // count the escapes : even # -> not escaped, odd -> escaped
  388. int i = 0;
  389. int len = t.Image.Length;
  390. i = t.Image.IndexOf('$');
  391. if (i == -1)
  392. {
  393. // yikes!
  394. runtimeServices.Error("ASTReference.getRoot() : internal error : no $ found for slashbang.");
  395. computableReference = false;
  396. nullString = t.Image;
  397. return nullString;
  398. }
  399. while(i < len && t.Image[i] != '\\')
  400. {
  401. i++;
  402. }
  403. // ok, i is the first \ char
  404. int start = i;
  405. int count = 0;
  406. while(i < len && t.Image[i++] == '\\')
  407. {
  408. count++;
  409. }
  410. // now construct the output string. We really don't care about leading
  411. // slashes as this is not a reference. It's quasi-schmoo
  412. nullString = t.Image.Substring(0, (start) - (0)); // prefix up to the first
  413. nullString += t.Image.Substring(start, (start + count - 1) - (start)); // get the slashes
  414. nullString += t.Image.Substring(start + count); // and the rest, including the
  415. // this isn't a valid reference, so lets short circuit the value and set calcs
  416. computableReference = false;
  417. return nullString;
  418. }
  419. // we need to see if this reference is escaped. if so
  420. // we will clean off the leading \'s and let the
  421. // regular behavior determine if we should output this
  422. // as \$foo or $foo later on in render(). Laziness..
  423. escaped = false;
  424. if (t.Image.StartsWith(@"\"))
  425. {
  426. // count the escapes : even # -> not escaped, odd -> escaped
  427. int i = 0;
  428. int len = t.Image.Length;
  429. while(i < len && t.Image[i] == '\\')
  430. {
  431. i++;
  432. }
  433. if ((i % 2) != 0)
  434. {
  435. escaped = true;
  436. }
  437. if (i > 0)
  438. {
  439. escPrefix = t.Image.Substring(0, (i / 2) - (0));
  440. }
  441. t.Image = t.Image.Substring(i);
  442. }
  443. // Look for preceeding stuff like '#' and '$'
  444. // and snip it off, except for the
  445. // last $
  446. int loc1 = t.Image.LastIndexOf('$');
  447. // if we have extra stuff, loc > 0
  448. // ex. '#$foo' so attach that to
  449. // the prefix.
  450. if (loc1 > 0)
  451. {
  452. morePrefix = morePrefix + t.Image.Substring(0, (loc1) - (0));
  453. t.Image = t.Image.Substring(loc1);
  454. }
  455. // Now it should be clean. Get the literal in case this reference
  456. // isn't backed by the context at runtime, and then figure out what
  457. // we are working with.
  458. nullString = Literal;
  459. if (t.Image.StartsWith("$!"))
  460. {
  461. referenceType = ReferenceType.Quiet;
  462. // only if we aren't escaped do we want to null the output
  463. if (!escaped)
  464. {
  465. nullString = string.Empty;
  466. }
  467. if (t.Image.StartsWith("$!{"))
  468. {
  469. // ex : $!{provider.Title}
  470. return t.Next.Image;
  471. }
  472. else
  473. {
  474. // ex : $!provider.Title
  475. return t.Image.Substring(2);
  476. }
  477. }
  478. else if (t.Image.Equals("${"))
  479. {
  480. // ex : ${provider.Title}
  481. referenceType = ReferenceType.Formal;
  482. return t.Next.Image;
  483. }
  484. else if (t.Image.StartsWith("$"))
  485. {
  486. // just nip off the '$' so we have
  487. // the root
  488. referenceType = ReferenceType.Normal;
  489. return t.Image.Substring(1);
  490. }
  491. else
  492. {
  493. // this is a 'RUNT', which can happen in certain circumstances where
  494. // the parser is fooled into believing that an IDENTIFIER is a real
  495. // reference. Another 'dreaded' MORE hack :).
  496. referenceType = ReferenceType.Runt;
  497. return t.Image;
  498. }
  499. }
  500. }
  501. }
  502. }