PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/syntax/TokenMarker.java

#
Java | 874 lines | 635 code | 109 blank | 130 comment | 179 complexity | 96611e0e232400ec325fd798fccbdb48 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 gnu.regexp.*;
  26. import javax.swing.text.Segment;
  27. import java.util.*;
  28. import org.gjt.sp.jedit.*;
  29. import org.gjt.sp.util.CharIndexedSegment;
  30. import org.gjt.sp.util.Log;
  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 4947 2003-12-27 05:14:46Z spestov $
  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
  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. return context;
  219. } //}}}
  220. //{{{ Private members
  221. //{{{ Instance variables
  222. private Hashtable ruleSets;
  223. private ParserRuleSet mainRuleSet;
  224. // Instead of passing these around to each method, we just store them
  225. // as instance variables. Note that this is not thread-safe.
  226. private TokenHandler tokenHandler;
  227. private Segment line;
  228. private LineContext context;
  229. private KeywordMap keywords;
  230. private Segment pattern = new Segment();
  231. private int lastOffset;
  232. private int lineLength;
  233. private int pos;
  234. private boolean escaped;
  235. private int whitespaceEnd;
  236. private boolean seenWhitespaceEnd;
  237. //}}}
  238. //{{{ checkDelegateEnd() method
  239. private boolean checkDelegateEnd(ParserRule rule)
  240. {
  241. if(rule.end == null)
  242. return false;
  243. LineContext tempContext = context;
  244. context = context.parent;
  245. keywords = context.rules.getKeywords();
  246. boolean tempEscaped = escaped;
  247. boolean b = handleRule(rule,true);
  248. context = tempContext;
  249. keywords = context.rules.getKeywords();
  250. if(b && !tempEscaped)
  251. {
  252. if(context.inRule != null)
  253. handleRule(context.inRule,true);
  254. markKeyword(true);
  255. context = (LineContext)context.parent.clone();
  256. tokenHandler.handleToken(line,
  257. (context.inRule.action & ParserRule.EXCLUDE_MATCH)
  258. == ParserRule.EXCLUDE_MATCH
  259. ? context.rules.getDefault()
  260. : context.inRule.token,
  261. pos - line.offset,pattern.count,context);
  262. keywords = context.rules.getKeywords();
  263. context.inRule = null;
  264. lastOffset = pos + pattern.count;
  265. // move pos to last character of match sequence
  266. pos += (pattern.count - 1);
  267. return true;
  268. }
  269. // check escape rule of parent
  270. if((rule.action & ParserRule.NO_ESCAPE) == 0)
  271. {
  272. ParserRule escape = context.parent.rules.getEscapeRule();
  273. if(escape != null && handleRule(escape,false))
  274. return true;
  275. }
  276. return false;
  277. } //}}}
  278. //{{{ handleRule() method
  279. /**
  280. * Checks if the rule matches the line at the current position
  281. * and handles the rule if it does match
  282. */
  283. private boolean handleRule(ParserRule checkRule, boolean end)
  284. {
  285. //{{{ Some rules can only match in certain locations
  286. if(!end)
  287. {
  288. if(Character.toUpperCase(checkRule.hashChar)
  289. != Character.toUpperCase(line.array[pos]))
  290. {
  291. return false;
  292. }
  293. }
  294. int offset = ((checkRule.action & ParserRule.MARK_PREVIOUS) != 0) ?
  295. lastOffset : pos;
  296. int posMatch = (end ? checkRule.endPosMatch : checkRule.startPosMatch);
  297. if((posMatch & ParserRule.AT_LINE_START)
  298. == ParserRule.AT_LINE_START)
  299. {
  300. if(offset != line.offset)
  301. return false;
  302. }
  303. else if((posMatch & ParserRule.AT_WHITESPACE_END)
  304. == ParserRule.AT_WHITESPACE_END)
  305. {
  306. if(offset != whitespaceEnd)
  307. return false;
  308. }
  309. else if((posMatch & ParserRule.AT_WORD_START)
  310. == ParserRule.AT_WORD_START)
  311. {
  312. if(offset != lastOffset)
  313. return false;
  314. } //}}}
  315. int matchedChars = 1;
  316. CharIndexedSegment charIndexed = null;
  317. REMatch match = null;
  318. //{{{ See if the rule's start or end sequence matches here
  319. if(!end || (checkRule.action & ParserRule.MARK_FOLLOWING) == 0)
  320. {
  321. // the end cannot be a regular expression
  322. if((checkRule.action & ParserRule.REGEXP) == 0 || end)
  323. {
  324. if(end)
  325. {
  326. if(context.spanEndSubst != null)
  327. pattern.array = context.spanEndSubst;
  328. else
  329. pattern.array = checkRule.end;
  330. }
  331. else
  332. pattern.array = checkRule.start;
  333. pattern.offset = 0;
  334. pattern.count = pattern.array.length;
  335. matchedChars = pattern.count;
  336. if(!SyntaxUtilities.regionMatches(context.rules
  337. .getIgnoreCase(),line,pos,pattern.array))
  338. {
  339. return false;
  340. }
  341. }
  342. else
  343. {
  344. // note that all regexps start with \A so they only
  345. // match the start of the string
  346. int matchStart = pos - line.offset;
  347. charIndexed = new CharIndexedSegment(line,matchStart);
  348. match = checkRule.startRegexp.getMatch(
  349. charIndexed,0,RE.REG_ANCHORINDEX);
  350. if(match == null)
  351. return false;
  352. else if(match.getStartIndex() != 0)
  353. throw new InternalError("Can't happen");
  354. else
  355. {
  356. matchedChars = match.getEndIndex();
  357. /* workaround for hang if match was
  358. * zero-width. not sure if there is
  359. * a better way to handle this */
  360. if(matchedChars == 0)
  361. matchedChars = 1;
  362. }
  363. }
  364. } //}}}
  365. //{{{ Check for an escape sequence
  366. if((checkRule.action & ParserRule.IS_ESCAPE) == ParserRule.IS_ESCAPE)
  367. {
  368. if(context.inRule != null)
  369. handleRule(context.inRule,true);
  370. escaped = !escaped;
  371. pos += pattern.count - 1;
  372. }
  373. else if(escaped)
  374. {
  375. escaped = false;
  376. pos += pattern.count - 1;
  377. } //}}}
  378. //{{{ Handle start of rule
  379. else if(!end)
  380. {
  381. if(context.inRule != null)
  382. handleRule(context.inRule,true);
  383. markKeyword((checkRule.action & ParserRule.MARK_PREVIOUS)
  384. != ParserRule.MARK_PREVIOUS);
  385. switch(checkRule.action & ParserRule.MAJOR_ACTIONS)
  386. {
  387. //{{{ SEQ
  388. case ParserRule.SEQ:
  389. context.spanEndSubst = null;
  390. if((checkRule.action & ParserRule.REGEXP) != 0)
  391. {
  392. handleTokenWithSpaces(tokenHandler,
  393. checkRule.token,
  394. pos - line.offset,
  395. matchedChars,
  396. context);
  397. }
  398. else
  399. {
  400. tokenHandler.handleToken(line,
  401. checkRule.token,
  402. pos - line.offset,
  403. matchedChars,context);
  404. }
  405. // a DELEGATE attribute on a SEQ changes the
  406. // ruleset from the end of the SEQ onwards
  407. if(checkRule.delegate != null)
  408. {
  409. context = new LineContext(
  410. checkRule.delegate,
  411. context.parent);
  412. keywords = context.rules.getKeywords();
  413. }
  414. break;
  415. //}}}
  416. //{{{ SPAN, EOL_SPAN
  417. case ParserRule.SPAN:
  418. case ParserRule.EOL_SPAN:
  419. context.inRule = checkRule;
  420. byte tokenType = ((checkRule.action & ParserRule.EXCLUDE_MATCH)
  421. == ParserRule.EXCLUDE_MATCH
  422. ? context.rules.getDefault() : checkRule.token);
  423. if((checkRule.action & ParserRule.REGEXP) != 0)
  424. {
  425. handleTokenWithSpaces(tokenHandler,
  426. tokenType,
  427. pos - line.offset,
  428. matchedChars,
  429. context);
  430. }
  431. else
  432. {
  433. tokenHandler.handleToken(line,tokenType,
  434. pos - line.offset,
  435. matchedChars,context);
  436. }
  437. char[] spanEndSubst = null;
  438. /* substitute result of matching the rule start
  439. * into the end string.
  440. *
  441. * eg, in shell script mode, <<\s*(\w+) is
  442. * matched into \<$1\> to construct rules for
  443. * highlighting read-ins like this <<EOF
  444. * ...
  445. * EOF
  446. */
  447. if(charIndexed != null && checkRule.end != null)
  448. {
  449. spanEndSubst = substitute(match,
  450. checkRule.end);
  451. }
  452. context.spanEndSubst = spanEndSubst;
  453. context = new LineContext(
  454. checkRule.delegate,
  455. context);
  456. keywords = context.rules.getKeywords();
  457. break;
  458. //}}}
  459. //{{{ MARK_FOLLOWING
  460. case ParserRule.MARK_FOLLOWING:
  461. tokenHandler.handleToken(line,(checkRule.action
  462. & ParserRule.EXCLUDE_MATCH)
  463. == ParserRule.EXCLUDE_MATCH ?
  464. context.rules.getDefault()
  465. : checkRule.token,pos - line.offset,
  466. pattern.count,context);
  467. context.spanEndSubst = null;
  468. context.inRule = checkRule;
  469. break;
  470. //}}}
  471. //{{{ MARK_PREVIOUS
  472. case ParserRule.MARK_PREVIOUS:
  473. context.spanEndSubst = null;
  474. if ((checkRule.action & ParserRule.EXCLUDE_MATCH)
  475. == ParserRule.EXCLUDE_MATCH)
  476. {
  477. if(pos != lastOffset)
  478. {
  479. tokenHandler.handleToken(line,
  480. checkRule.token,
  481. lastOffset - line.offset,
  482. pos - lastOffset,
  483. context);
  484. }
  485. tokenHandler.handleToken(line,
  486. context.rules.getDefault(),
  487. pos - line.offset,pattern.count,
  488. context);
  489. }
  490. else
  491. {
  492. tokenHandler.handleToken(line,
  493. checkRule.token,
  494. lastOffset - line.offset,
  495. pos - lastOffset + pattern.count,
  496. context);
  497. }
  498. break;
  499. //}}}
  500. default:
  501. throw new InternalError("Unhandled major action");
  502. }
  503. // move pos to last character of match sequence
  504. pos += (matchedChars - 1);
  505. lastOffset = pos + 1;
  506. // break out of inner for loop to check next char
  507. } //}}}
  508. //{{{ Handle end of MARK_FOLLOWING
  509. else if((context.inRule.action & ParserRule.MARK_FOLLOWING) != 0)
  510. {
  511. if(pos != lastOffset)
  512. {
  513. tokenHandler.handleToken(line,
  514. context.inRule.token,
  515. lastOffset - line.offset,
  516. pos - lastOffset,context);
  517. }
  518. lastOffset = pos;
  519. context.inRule = null;
  520. } //}}}
  521. return true;
  522. } //}}}
  523. //{{{ handleNoWordBreak() method
  524. private void handleNoWordBreak()
  525. {
  526. if(context.parent != null)
  527. {
  528. ParserRule rule = context.parent.inRule;
  529. if(rule != null && (context.parent.inRule.action
  530. & ParserRule.NO_WORD_BREAK) != 0)
  531. {
  532. if(pos != lastOffset)
  533. {
  534. tokenHandler.handleToken(line,
  535. rule.token,
  536. lastOffset - line.offset,
  537. pos - lastOffset,context);
  538. }
  539. lastOffset = pos;
  540. context = context.parent;
  541. keywords = context.rules.getKeywords();
  542. context.inRule = null;
  543. }
  544. }
  545. } //}}}
  546. //{{{ handleTokenWithSpaces() method
  547. private void handleTokenWithSpaces(TokenHandler tokenHandler,
  548. byte tokenType, int start, int len, LineContext context)
  549. {
  550. int last = start;
  551. int end = start + len;
  552. for(int i = start; i < end; i++)
  553. {
  554. if(Character.isWhitespace(line.array[i + line.offset]))
  555. {
  556. if(last != i)
  557. {
  558. tokenHandler.handleToken(line,
  559. tokenType,last,i - last,context);
  560. }
  561. tokenHandler.handleToken(line,tokenType,i,1,context);
  562. last = i + 1;
  563. }
  564. }
  565. if(last != end)
  566. {
  567. tokenHandler.handleToken(line,tokenType,last,
  568. end - last,context);
  569. }
  570. } //}}}
  571. //{{{ markKeyword() method
  572. private void markKeyword(boolean addRemaining)
  573. {
  574. int len = pos - lastOffset;
  575. if(len == 0)
  576. return;
  577. //{{{ Do digits
  578. if(context.rules.getHighlightDigits())
  579. {
  580. boolean digit = false;
  581. boolean mixed = false;
  582. for(int i = lastOffset; i < pos; i++)
  583. {
  584. char ch = line.array[i];
  585. if(Character.isDigit(ch))
  586. digit = true;
  587. else
  588. mixed = true;
  589. }
  590. if(mixed)
  591. {
  592. RE digitRE = context.rules.getDigitRegexp();
  593. // only match against regexp if its not all
  594. // digits; if all digits, no point matching
  595. if(digit)
  596. {
  597. if(digitRE == null)
  598. {
  599. // mixed digit/alpha keyword,
  600. // and no regexp... don't
  601. // highlight as DIGIT
  602. digit = false;
  603. }
  604. else
  605. {
  606. CharIndexedSegment seg = new CharIndexedSegment(
  607. line,false);
  608. int oldCount = line.count;
  609. int oldOffset = line.offset;
  610. line.offset = lastOffset;
  611. line.count = len;
  612. if(!digitRE.isMatch(seg))
  613. digit = false;
  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 char[] substitute(REMatch 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.toString(
  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 boolean charArraysEqual(char[] c1, char[] c2)
  750. {
  751. if(c1 == null)
  752. return (c2 == null);
  753. else if(c2 == null)
  754. return (c1 == null);
  755. if(c1.length != c2.length)
  756. return false;
  757. for(int i = 0; i < c1.length; i++)
  758. {
  759. if(c1[i] != c2[i])
  760. return false;
  761. }
  762. return true;
  763. } //}}}
  764. } //}}}
  765. }