PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/syntax/TokenMarker.java

#
Java | 877 lines | 633 code | 111 blank | 133 comment | 175 complexity | abc4c21bd6f0558116512b3188234f00 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * TokenMarker.java - Tokenizes lines of text
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1998, 2003 Slava Pestov
  7. * Copyright (C) 1999, 2000 mike dillon
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package org.gjt.sp.jedit.syntax;
  24. //{{{ Imports
  25. import javax.swing.text.Segment;
  26. import java.util.*;
  27. import java.util.regex.Matcher;
  28. import java.util.regex.Pattern;
  29. import org.gjt.sp.jedit.*;
  30. import org.gjt.sp.util.SegmentCharSequence;
  31. //}}}
  32. /**
  33. * A token marker splits lines of text into tokens. Each token carries
  34. * a length field and an identification tag that can be mapped to a color
  35. * or font style for painting that token.
  36. *
  37. * @author Slava Pestov, mike dillon
  38. * @version $Id: TokenMarker.java 5488 2006-06-24 04:01:01Z vanza $
  39. *
  40. * @see org.gjt.sp.jedit.syntax.Token
  41. * @see org.gjt.sp.jedit.syntax.TokenHandler
  42. */
  43. public class TokenMarker
  44. {
  45. //{{{ TokenMarker constructor
  46. public TokenMarker()
  47. {
  48. ruleSets = new Hashtable(64);
  49. } //}}}
  50. //{{{ addRuleSet() method
  51. public void addRuleSet(ParserRuleSet rules)
  52. {
  53. ruleSets.put(rules.getSetName(), rules);
  54. if (rules.getSetName().equals("MAIN"))
  55. mainRuleSet = rules;
  56. } //}}}
  57. //{{{ getMainRuleSet() method
  58. public ParserRuleSet getMainRuleSet()
  59. {
  60. return mainRuleSet;
  61. } //}}}
  62. //{{{ getRuleSet() method
  63. public ParserRuleSet getRuleSet(String setName)
  64. {
  65. return (ParserRuleSet) ruleSets.get(setName);
  66. } //}}}
  67. //{{{ getRuleSets() method
  68. /**
  69. * @since jEdit 4.2pre3
  70. */
  71. public ParserRuleSet[] getRuleSets()
  72. {
  73. return (ParserRuleSet[])ruleSets.values().toArray(new ParserRuleSet[ruleSets.size()]);
  74. } //}}}
  75. //{{{ markTokens() method
  76. /**
  77. * Do not call this method directly; call Buffer.markTokens() instead.
  78. */
  79. public LineContext markTokens(LineContext prevContext,
  80. TokenHandler tokenHandler, Segment line)
  81. {
  82. //{{{ Set up some instance variables
  83. // this is to avoid having to pass around lots and lots of
  84. // parameters.
  85. this.tokenHandler = tokenHandler;
  86. this.line = line;
  87. lastOffset = line.offset;
  88. lineLength = line.count + line.offset;
  89. context = new LineContext();
  90. if(prevContext == null)
  91. context.rules = getMainRuleSet();
  92. else
  93. {
  94. context.parent = prevContext.parent;
  95. context.inRule = prevContext.inRule;
  96. context.rules = prevContext.rules;
  97. context.spanEndSubst = prevContext.spanEndSubst;
  98. }
  99. keywords = context.rules.getKeywords();
  100. escaped = false;
  101. seenWhitespaceEnd = false;
  102. whitespaceEnd = line.offset;
  103. //}}}
  104. //{{{ Main parser loop
  105. ParserRule rule;
  106. int terminateChar = context.rules.getTerminateChar();
  107. boolean terminated = false;
  108. main_loop: for(pos = line.offset; pos < lineLength; pos++)
  109. {
  110. //{{{ check if we have to stop parsing (happens if the terminateChar has been exceeded)
  111. if(terminateChar >= 0 && pos - line.offset >= terminateChar
  112. && !terminated)
  113. {
  114. terminated = true;
  115. context = new LineContext(ParserRuleSet
  116. .getStandardRuleSet(context.rules
  117. .getDefault()),context);
  118. keywords = context.rules.getKeywords();
  119. } //}}}
  120. //{{{ check for end of delegate
  121. if(context.parent != null)
  122. {
  123. rule = context.parent.inRule;
  124. if(rule != null)
  125. {
  126. if(checkDelegateEnd(rule))
  127. {
  128. seenWhitespaceEnd = true;
  129. continue main_loop;
  130. }
  131. }
  132. } //}}}
  133. //{{{ check every rule
  134. char ch = line.array[pos];
  135. rule = context.rules.getRules(ch);
  136. while(rule != null)
  137. {
  138. // stop checking rules if there was a match
  139. if (handleRule(rule,false))
  140. {
  141. seenWhitespaceEnd = true;
  142. continue main_loop;
  143. }
  144. rule = rule.next;
  145. } //}}}
  146. //{{{ check if current character is a word separator
  147. if(Character.isWhitespace(ch))
  148. {
  149. if(!seenWhitespaceEnd)
  150. whitespaceEnd = pos + 1;
  151. if(context.inRule != null)
  152. handleRule(context.inRule,true);
  153. handleNoWordBreak();
  154. markKeyword(false);
  155. if(lastOffset != pos)
  156. {
  157. tokenHandler.handleToken(line,
  158. context.rules.getDefault(),
  159. lastOffset - line.offset,
  160. pos - lastOffset,
  161. context);
  162. }
  163. tokenHandler.handleToken(line,
  164. context.rules.getDefault(),
  165. pos - line.offset,1,context);
  166. lastOffset = pos + 1;
  167. escaped = false;
  168. }
  169. else
  170. {
  171. if(keywords != null || context.rules.getRuleCount() != 0)
  172. {
  173. String noWordSep = context.rules.getNoWordSep();
  174. if(!Character.isLetterOrDigit(ch)
  175. && noWordSep.indexOf(ch) == -1)
  176. {
  177. if(context.inRule != null)
  178. handleRule(context.inRule,true);
  179. handleNoWordBreak();
  180. markKeyword(true);
  181. tokenHandler.handleToken(line,
  182. context.rules.getDefault(),
  183. lastOffset - line.offset,1,
  184. context);
  185. lastOffset = pos + 1;
  186. }
  187. }
  188. seenWhitespaceEnd = true;
  189. escaped = false;
  190. } //}}}
  191. } //}}}
  192. //{{{ Mark all remaining characters
  193. pos = lineLength;
  194. if(context.inRule != null)
  195. handleRule(context.inRule,true);
  196. handleNoWordBreak();
  197. markKeyword(true);
  198. //}}}
  199. //{{{ Unwind any NO_LINE_BREAK parent delegates
  200. unwind: while(context.parent != null)
  201. {
  202. rule = context.parent.inRule;
  203. if((rule != null && (rule.action
  204. & ParserRule.NO_LINE_BREAK) == ParserRule.NO_LINE_BREAK)
  205. || terminated)
  206. {
  207. context = context.parent;
  208. keywords = context.rules.getKeywords();
  209. context.inRule = null;
  210. }
  211. else
  212. break unwind;
  213. } //}}}
  214. tokenHandler.handleToken(line,Token.END,
  215. pos - line.offset,0,context);
  216. context = context.intern();
  217. tokenHandler.setLineContext(context);
  218. /* for GC. */
  219. this.line = null;
  220. return context;
  221. } //}}}
  222. //{{{ Private members
  223. //{{{ Instance variables
  224. private Hashtable ruleSets;
  225. private ParserRuleSet mainRuleSet;
  226. // Instead of passing these around to each method, we just store them
  227. // as instance variables. Note that this is not thread-safe.
  228. private TokenHandler tokenHandler;
  229. private Segment line;
  230. private LineContext context;
  231. private KeywordMap keywords;
  232. private Segment pattern = new Segment();
  233. private int lastOffset;
  234. private int lineLength;
  235. private int pos;
  236. private boolean escaped;
  237. private int whitespaceEnd;
  238. private boolean seenWhitespaceEnd;
  239. //}}}
  240. //{{{ checkDelegateEnd() method
  241. private boolean checkDelegateEnd(ParserRule rule)
  242. {
  243. if(rule.end == null)
  244. return false;
  245. LineContext tempContext = context;
  246. context = context.parent;
  247. keywords = context.rules.getKeywords();
  248. boolean tempEscaped = escaped;
  249. boolean b = handleRule(rule,true);
  250. context = tempContext;
  251. keywords = context.rules.getKeywords();
  252. if(b && !tempEscaped)
  253. {
  254. if(context.inRule != null)
  255. handleRule(context.inRule,true);
  256. markKeyword(true);
  257. context = (LineContext)context.parent.clone();
  258. tokenHandler.handleToken(line,
  259. (context.inRule.action & ParserRule.EXCLUDE_MATCH)
  260. == ParserRule.EXCLUDE_MATCH
  261. ? context.rules.getDefault()
  262. : context.inRule.token,
  263. pos - line.offset,pattern.count,context);
  264. keywords = context.rules.getKeywords();
  265. context.inRule = null;
  266. lastOffset = pos + pattern.count;
  267. // move pos to last character of match sequence
  268. pos += (pattern.count - 1);
  269. return true;
  270. }
  271. // check escape rule of parent
  272. if((rule.action & ParserRule.NO_ESCAPE) == 0)
  273. {
  274. ParserRule escape = context.parent.rules.getEscapeRule();
  275. if(escape != null && handleRule(escape,false))
  276. return true;
  277. }
  278. return false;
  279. } //}}}
  280. //{{{ handleRule() method
  281. /**
  282. * Checks if the rule matches the line at the current position
  283. * and handles the rule if it does match
  284. */
  285. private boolean handleRule(ParserRule checkRule, boolean end)
  286. {
  287. //{{{ Some rules can only match in certain locations
  288. if(!end)
  289. {
  290. if(Character.toUpperCase(checkRule.hashChar)
  291. != Character.toUpperCase(line.array[pos]))
  292. {
  293. return false;
  294. }
  295. }
  296. int offset = ((checkRule.action & ParserRule.MARK_PREVIOUS) != 0) ?
  297. lastOffset : pos;
  298. int posMatch = (end ? checkRule.endPosMatch : checkRule.startPosMatch);
  299. if((posMatch & ParserRule.AT_LINE_START)
  300. == ParserRule.AT_LINE_START)
  301. {
  302. if(offset != line.offset)
  303. return false;
  304. }
  305. else if((posMatch & ParserRule.AT_WHITESPACE_END)
  306. == ParserRule.AT_WHITESPACE_END)
  307. {
  308. if(offset != whitespaceEnd)
  309. return false;
  310. }
  311. else if((posMatch & ParserRule.AT_WORD_START)
  312. == ParserRule.AT_WORD_START)
  313. {
  314. if(offset != lastOffset)
  315. return false;
  316. } //}}}
  317. int matchedChars = 1;
  318. CharSequence charSeq = null;
  319. Matcher match = null;
  320. //{{{ See if the rule's start or end sequence matches here
  321. if(!end || (checkRule.action & ParserRule.MARK_FOLLOWING) == 0)
  322. {
  323. // the end cannot be a regular expression
  324. if((checkRule.action & ParserRule.REGEXP) == 0 || end)
  325. {
  326. if(end)
  327. {
  328. if(context.spanEndSubst != null)
  329. pattern.array = context.spanEndSubst;
  330. else
  331. pattern.array = checkRule.end;
  332. }
  333. else
  334. pattern.array = checkRule.start;
  335. pattern.offset = 0;
  336. pattern.count = pattern.array.length;
  337. matchedChars = pattern.count;
  338. if(!SyntaxUtilities.regionMatches(context.rules
  339. .getIgnoreCase(),line,pos,pattern.array))
  340. {
  341. return false;
  342. }
  343. }
  344. else
  345. {
  346. // note that all regexps start with \A so they only
  347. // match the start of the string
  348. //int matchStart = pos - line.offset;
  349. charSeq = new SegmentCharSequence(line, pos - line.offset,
  350. line.count - (pos - line.offset));
  351. match = checkRule.startRegexp.matcher(charSeq);
  352. if(!match.lookingAt())
  353. return false;
  354. else if(match.start() != 0)
  355. throw new InternalError("Can't happen");
  356. else
  357. {
  358. matchedChars = match.end();
  359. /* workaround for hang if match was
  360. * zero-width. not sure if there is
  361. * a better way to handle this */
  362. if(matchedChars == 0)
  363. matchedChars = 1;
  364. }
  365. }
  366. } //}}}
  367. //{{{ Check for an escape sequence
  368. if((checkRule.action & ParserRule.IS_ESCAPE) == ParserRule.IS_ESCAPE)
  369. {
  370. if(context.inRule != null)
  371. handleRule(context.inRule,true);
  372. escaped = !escaped;
  373. pos += pattern.count - 1;
  374. }
  375. else if(escaped)
  376. {
  377. escaped = false;
  378. pos += pattern.count - 1;
  379. } //}}}
  380. //{{{ Handle start of rule
  381. else if(!end)
  382. {
  383. if(context.inRule != null)
  384. handleRule(context.inRule,true);
  385. markKeyword((checkRule.action & ParserRule.MARK_PREVIOUS)
  386. != ParserRule.MARK_PREVIOUS);
  387. switch(checkRule.action & ParserRule.MAJOR_ACTIONS)
  388. {
  389. //{{{ SEQ
  390. case ParserRule.SEQ:
  391. context.spanEndSubst = null;
  392. if((checkRule.action & ParserRule.REGEXP) != 0)
  393. {
  394. handleTokenWithSpaces(tokenHandler,
  395. checkRule.token,
  396. pos - line.offset,
  397. matchedChars,
  398. context);
  399. }
  400. else
  401. {
  402. tokenHandler.handleToken(line,
  403. checkRule.token,
  404. pos - line.offset,
  405. matchedChars,context);
  406. }
  407. // a DELEGATE attribute on a SEQ changes the
  408. // ruleset from the end of the SEQ onwards
  409. if(checkRule.delegate != null)
  410. {
  411. context = new LineContext(
  412. checkRule.delegate,
  413. context.parent);
  414. keywords = context.rules.getKeywords();
  415. }
  416. break;
  417. //}}}
  418. //{{{ SPAN, EOL_SPAN
  419. case ParserRule.SPAN:
  420. case ParserRule.EOL_SPAN:
  421. context.inRule = checkRule;
  422. byte tokenType = ((checkRule.action & ParserRule.EXCLUDE_MATCH)
  423. == ParserRule.EXCLUDE_MATCH
  424. ? context.rules.getDefault() : checkRule.token);
  425. if((checkRule.action & ParserRule.REGEXP) != 0)
  426. {
  427. handleTokenWithSpaces(tokenHandler,
  428. tokenType,
  429. pos - line.offset,
  430. matchedChars,
  431. context);
  432. }
  433. else
  434. {
  435. tokenHandler.handleToken(line,tokenType,
  436. pos - line.offset,
  437. matchedChars,context);
  438. }
  439. char[] spanEndSubst = null;
  440. /* substitute result of matching the rule start
  441. * into the end string.
  442. *
  443. * eg, in shell script mode, <<\s*(\w+) is
  444. * matched into \<$1\> to construct rules for
  445. * highlighting read-ins like this <<EOF
  446. * ...
  447. * EOF
  448. */
  449. if(charSeq != null && checkRule.end != null)
  450. {
  451. spanEndSubst = substitute(match,
  452. checkRule.end);
  453. }
  454. context.spanEndSubst = spanEndSubst;
  455. context = new LineContext(
  456. checkRule.delegate,
  457. context);
  458. keywords = context.rules.getKeywords();
  459. break;
  460. //}}}
  461. //{{{ MARK_FOLLOWING
  462. case ParserRule.MARK_FOLLOWING:
  463. tokenHandler.handleToken(line,(checkRule.action
  464. & ParserRule.EXCLUDE_MATCH)
  465. == ParserRule.EXCLUDE_MATCH ?
  466. context.rules.getDefault()
  467. : checkRule.token,pos - line.offset,
  468. pattern.count,context);
  469. context.spanEndSubst = null;
  470. context.inRule = checkRule;
  471. break;
  472. //}}}
  473. //{{{ MARK_PREVIOUS
  474. case ParserRule.MARK_PREVIOUS:
  475. context.spanEndSubst = null;
  476. if ((checkRule.action & ParserRule.EXCLUDE_MATCH)
  477. == ParserRule.EXCLUDE_MATCH)
  478. {
  479. if(pos != lastOffset)
  480. {
  481. tokenHandler.handleToken(line,
  482. checkRule.token,
  483. lastOffset - line.offset,
  484. pos - lastOffset,
  485. context);
  486. }
  487. tokenHandler.handleToken(line,
  488. context.rules.getDefault(),
  489. pos - line.offset,pattern.count,
  490. context);
  491. }
  492. else
  493. {
  494. tokenHandler.handleToken(line,
  495. checkRule.token,
  496. lastOffset - line.offset,
  497. pos - lastOffset + pattern.count,
  498. context);
  499. }
  500. break;
  501. //}}}
  502. default:
  503. throw new InternalError("Unhandled major action");
  504. }
  505. // move pos to last character of match sequence
  506. pos += (matchedChars - 1);
  507. lastOffset = pos + 1;
  508. // break out of inner for loop to check next char
  509. } //}}}
  510. //{{{ Handle end of MARK_FOLLOWING
  511. else if((context.inRule.action & ParserRule.MARK_FOLLOWING) != 0)
  512. {
  513. if(pos != lastOffset)
  514. {
  515. tokenHandler.handleToken(line,
  516. context.inRule.token,
  517. lastOffset - line.offset,
  518. pos - lastOffset,context);
  519. }
  520. lastOffset = pos;
  521. context.inRule = null;
  522. } //}}}
  523. return true;
  524. } //}}}
  525. //{{{ handleNoWordBreak() method
  526. private void handleNoWordBreak()
  527. {
  528. if(context.parent != null)
  529. {
  530. ParserRule rule = context.parent.inRule;
  531. if(rule != null && (context.parent.inRule.action
  532. & ParserRule.NO_WORD_BREAK) != 0)
  533. {
  534. if(pos != lastOffset)
  535. {
  536. tokenHandler.handleToken(line,
  537. rule.token,
  538. lastOffset - line.offset,
  539. pos - lastOffset,context);
  540. }
  541. lastOffset = pos;
  542. context = context.parent;
  543. keywords = context.rules.getKeywords();
  544. context.inRule = null;
  545. }
  546. }
  547. } //}}}
  548. //{{{ handleTokenWithSpaces() method
  549. private void handleTokenWithSpaces(TokenHandler tokenHandler,
  550. byte tokenType, int start, int len, LineContext context)
  551. {
  552. int last = start;
  553. int end = start + len;
  554. for(int i = start; i < end; i++)
  555. {
  556. if(Character.isWhitespace(line.array[i + line.offset]))
  557. {
  558. if(last != i)
  559. {
  560. tokenHandler.handleToken(line,
  561. tokenType,last,i - last,context);
  562. }
  563. tokenHandler.handleToken(line,tokenType,i,1,context);
  564. last = i + 1;
  565. }
  566. }
  567. if(last != end)
  568. {
  569. tokenHandler.handleToken(line,tokenType,last,
  570. end - last,context);
  571. }
  572. } //}}}
  573. //{{{ markKeyword() method
  574. private void markKeyword(boolean addRemaining)
  575. {
  576. int len = pos - lastOffset;
  577. if(len == 0)
  578. return;
  579. //{{{ Do digits
  580. if(context.rules.getHighlightDigits())
  581. {
  582. boolean digit = false;
  583. boolean mixed = false;
  584. for(int i = lastOffset; i < pos; i++)
  585. {
  586. char ch = line.array[i];
  587. if(Character.isDigit(ch))
  588. digit = true;
  589. else
  590. mixed = true;
  591. }
  592. if(mixed)
  593. {
  594. Pattern digitRE = context.rules.getDigitRegexp();
  595. // only match against regexp if its not all
  596. // digits; if all digits, no point matching
  597. if(digit)
  598. {
  599. if(digitRE == null)
  600. {
  601. // mixed digit/alpha keyword,
  602. // and no regexp... don't
  603. // highlight as DIGIT
  604. digit = false;
  605. }
  606. else
  607. {
  608. int oldCount = line.count;
  609. int oldOffset = line.offset;
  610. line.offset = lastOffset;
  611. line.count = len;
  612. CharSequence seq = new SegmentCharSequence(line);
  613. digit = digitRE.matcher(seq).matches();
  614. line.offset = oldOffset;
  615. line.count = oldCount;
  616. }
  617. }
  618. }
  619. if(digit)
  620. {
  621. tokenHandler.handleToken(line,Token.DIGIT,
  622. lastOffset - line.offset,
  623. len,context);
  624. lastOffset = pos;
  625. return;
  626. }
  627. } //}}}
  628. //{{{ Do keywords
  629. if(keywords != null)
  630. {
  631. byte id = keywords.lookup(line, lastOffset, len);
  632. if(id != Token.NULL)
  633. {
  634. tokenHandler.handleToken(line,id,
  635. lastOffset - line.offset,
  636. len,context);
  637. lastOffset = pos;
  638. return;
  639. }
  640. } //}}}
  641. //{{{ Handle any remaining crud
  642. if(addRemaining)
  643. {
  644. tokenHandler.handleToken(line,context.rules.getDefault(),
  645. lastOffset - line.offset,len,context);
  646. lastOffset = pos;
  647. } //}}}
  648. } //}}}
  649. //{{{ substitute() method
  650. private static char[] substitute(Matcher match, char[] end)
  651. {
  652. StringBuffer buf = new StringBuffer();
  653. for(int i = 0; i < end.length; i++)
  654. {
  655. char ch = end[i];
  656. if(ch == '$')
  657. {
  658. if(i == end.length - 1)
  659. buf.append(ch);
  660. else
  661. {
  662. char digit = end[i + 1];
  663. if(!Character.isDigit(digit))
  664. buf.append(ch);
  665. else
  666. {
  667. buf.append(match.group(
  668. digit - '0'));
  669. i++;
  670. }
  671. }
  672. }
  673. else
  674. buf.append(ch);
  675. }
  676. char[] returnValue = new char[buf.length()];
  677. buf.getChars(0,buf.length(),returnValue,0);
  678. return returnValue;
  679. } //}}}
  680. //}}}
  681. //{{{ LineContext class
  682. /**
  683. * Stores persistent per-line syntax parser state.
  684. */
  685. public static class LineContext
  686. {
  687. private static Hashtable intern = new Hashtable();
  688. public LineContext parent;
  689. public ParserRule inRule;
  690. public ParserRuleSet rules;
  691. // used for SPAN_REGEXP rules; otherwise null
  692. public char[] spanEndSubst;
  693. //{{{ LineContext constructor
  694. public LineContext(ParserRuleSet rs, LineContext lc)
  695. {
  696. rules = rs;
  697. parent = (lc == null ? null : (LineContext)lc.clone());
  698. } //}}}
  699. //{{{ LineContext constructor
  700. public LineContext()
  701. {
  702. } //}}}
  703. //{{{ intern() method
  704. public LineContext intern()
  705. {
  706. Object obj = intern.get(this);
  707. if(obj == null)
  708. {
  709. intern.put(this,this);
  710. return this;
  711. }
  712. else
  713. return (LineContext)obj;
  714. } //}}}
  715. //{{{ hashCode() method
  716. public int hashCode()
  717. {
  718. if(inRule != null)
  719. return inRule.hashCode();
  720. else if(rules != null)
  721. return rules.hashCode();
  722. else
  723. return 0;
  724. } //}}}
  725. //{{{ equals() method
  726. public boolean equals(Object obj)
  727. {
  728. if(obj instanceof LineContext)
  729. {
  730. LineContext lc = (LineContext)obj;
  731. return lc.inRule == inRule && lc.rules == rules
  732. && MiscUtilities.objectsEqual(parent,lc.parent)
  733. && charArraysEqual(spanEndSubst,lc.spanEndSubst);
  734. }
  735. else
  736. return false;
  737. } //}}}
  738. //{{{ clone() method
  739. public Object clone()
  740. {
  741. LineContext lc = new LineContext();
  742. lc.inRule = inRule;
  743. lc.rules = rules;
  744. lc.parent = (parent == null) ? null : (LineContext) parent.clone();
  745. lc.spanEndSubst = spanEndSubst;
  746. return lc;
  747. } //}}}
  748. //{{{ charArraysEqual() method
  749. private static boolean charArraysEqual(char[] c1, char[] c2)
  750. {
  751. if(c1 == null)
  752. return c2 == null;
  753. // c1 is not null
  754. if(c2 == null)
  755. return false;
  756. if(c1.length != c2.length)
  757. return false;
  758. for(int i = 0; i < c1.length; i++)
  759. {
  760. if(c1[i] != c2[i])
  761. return false;
  762. }
  763. return true;
  764. } //}}}
  765. } //}}}
  766. }