1. /*
  2. * $Id: ParentNode.java,v 1.4 2001/04/30 21:47:57 edwingo Exp $
  3. *
  4. * The Apache Software License, Version 1.1
  5. *
  6. *
  7. * Copyright (c) 2000 The Apache Software Foundation. All rights
  8. * reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions
  12. * are met:
  13. *
  14. * 1. Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. *
  17. * 2. Redistributions in binary form must reproduce the above copyright
  18. * notice, this list of conditions and the following disclaimer in
  19. * the documentation and/or other materials provided with the
  20. * distribution.
  21. *
  22. * 3. The end-user documentation included with the redistribution,
  23. * if any, must include the following acknowledgment:
  24. * "This product includes software developed by the
  25. * Apache Software Foundation (http://www.apache.org/)."
  26. * Alternately, this acknowledgment may appear in the software itself,
  27. * if and wherever such third-party acknowledgments normally appear.
  28. *
  29. * 4. The names "Crimson" and "Apache Software Foundation" must
  30. * not be used to endorse or promote products derived from this
  31. * software without prior written permission. For written
  32. * permission, please contact apache@apache.org.
  33. *
  34. * 5. Products derived from this software may not be called "Apache",
  35. * nor may "Apache" appear in their name, without prior written
  36. * permission of the Apache Software Foundation.
  37. *
  38. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  39. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  40. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  41. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  42. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  43. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  44. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  45. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  46. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  47. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  48. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  49. * SUCH DAMAGE.
  50. * ====================================================================
  51. *
  52. * This software consists of voluntary contributions made by many
  53. * individuals on behalf of the Apache Software Foundation and was
  54. * originally based on software copyright (c) 1999, Sun Microsystems, Inc.,
  55. * http://www.sun.com. For more information on the Apache Software
  56. * Foundation, please see <http://www.apache.org/>.
  57. */
  58. package org.apache.crimson.tree;
  59. import java.io.IOException;
  60. import java.io.Writer;
  61. import org.xml.sax.SAXException;
  62. import org.w3c.dom.*;
  63. /**
  64. * This adds an implementation of "parent of" relationships to the NodeBase
  65. * class. It implements operations for maintaining a set of children,
  66. * providing indexed access to them, and writing them them out as text.
  67. *
  68. * <P> The NodeList it implements to describe its children is "live", as
  69. * required by DOM. That means that indexed accesses by applications must
  70. * handle cases associated with unstable indices and lengths. Indices
  71. * should not be stored if they can be invalidated by changes, including
  72. * changes made by other threads.
  73. *
  74. * @author David Brownell
  75. * @version $Revision: 1.4 $
  76. */
  77. // not public ... javadoc looks a bit odd (hidden base class)
  78. // but it's only subclassable within this package anyway
  79. abstract class ParentNode extends NodeBase
  80. {
  81. private NodeBase children [];
  82. private int length;
  83. /**
  84. * Builds a ParentNode, which can have children that are
  85. * subclasses of NodeBase.
  86. */
  87. // package private
  88. ParentNode () { }
  89. /**
  90. * Called to minimize space utilization. Affects only
  91. * this node; children must be individually trimmed.
  92. */
  93. public void trimToSize ()
  94. {
  95. if (length == 0)
  96. children = null;
  97. else if (children.length != length) {
  98. NodeBase temp [] = new NodeBase [length];
  99. System.arraycopy (children, 0, temp, 0, length);
  100. children = temp;
  101. }
  102. }
  103. // package private
  104. void reduceWaste ()
  105. {
  106. if (children == null)
  107. return;
  108. //
  109. // Arbitrary -- rather than paying trimToSize() costs
  110. // on most elements, we routinely accept some waste but
  111. // do try to reduce egregious waste. Interacts with
  112. // the array allocation done in appendChild.
  113. //
  114. if ((children.length - length) > 6)
  115. trimToSize ();
  116. }
  117. /**
  118. * Writes each of the child nodes. For element nodes, this adds
  119. * whitespace to indent non-text children (it prettyprints) unless
  120. * the <em>xml:space='preserve'</em> attribute applies, or the
  121. * write context disables prettyprinting.
  122. *
  123. * @param context describes how the children should be printed
  124. */
  125. public void writeChildrenXml (XmlWriteContext context) throws IOException
  126. {
  127. if (children == null)
  128. return;
  129. int oldIndent = 0;
  130. boolean preserve = true;
  131. boolean pureText = true;
  132. if (getNodeType () == ELEMENT_NODE) {
  133. preserve = "preserve".equals (
  134. getInheritedAttribute ("xml:space"));
  135. oldIndent = context.getIndentLevel ();
  136. }
  137. try {
  138. if (!preserve)
  139. context.setIndentLevel (oldIndent + 2);
  140. for (int i = 0; i < length; i++) {
  141. if (!preserve && children [i].getNodeType () != TEXT_NODE) {
  142. context.printIndent ();
  143. pureText = false;
  144. }
  145. children [i].writeXml (context);
  146. }
  147. } finally {
  148. if (!preserve) {
  149. context.setIndentLevel (oldIndent);
  150. if (!pureText)
  151. context.printIndent (); // for ETag
  152. }
  153. }
  154. }
  155. // package private -- overridden in implementation classes
  156. abstract void checkChildType (int type)
  157. throws DOMException;
  158. // DOM support
  159. /**
  160. * <b>DOM:</b> Returns true if there are children to this node.
  161. */
  162. final public boolean hasChildNodes ()
  163. {
  164. return length > 0;
  165. }
  166. /**
  167. * <b>DOM:</b> Returns the first child of this node, else null if there
  168. * are no children.
  169. */
  170. final public Node getFirstChild ()
  171. {
  172. if (length == 0)
  173. return null;
  174. return children [0];
  175. }
  176. /**
  177. * <b>DOM:</b> Returns the last child of this node, else null if there
  178. * are no children.
  179. */
  180. final public Node getLastChild ()
  181. {
  182. if (length == 0)
  183. return null;
  184. return children [length - 1];
  185. }
  186. /** <b>DOM:</b> Returns the number of children */
  187. final public int getLength ()
  188. {
  189. return length;
  190. }
  191. /** <b>DOM:</b> Returns the Nth child, or null */
  192. final public Node item (int i)
  193. {
  194. if (length == 0 || i >= length)
  195. return null;
  196. try {
  197. return children [i];
  198. } catch (ArrayIndexOutOfBoundsException e) {
  199. return null;
  200. }
  201. }
  202. // groups all the "wrong document/implementation" checks
  203. private NodeBase checkDocument (Node newChild)
  204. throws DOMException
  205. {
  206. if (newChild == null)
  207. throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
  208. // check for wrong implementation
  209. if (!(newChild instanceof NodeBase))
  210. throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
  211. Document owner = newChild.getOwnerDocument ();
  212. XmlDocument myOwner = ownerDocument;
  213. NodeBase child = (NodeBase) newChild;
  214. // bizarre DOM special case for document
  215. if (myOwner == null && this instanceof XmlDocument)
  216. myOwner = (XmlDocument) this;
  217. // check for wrong document
  218. if (owner != null && owner != myOwner)
  219. throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
  220. // permit "unowned" NodeBase children to be added,
  221. // e.g. if someone constructs an ElementNode directly
  222. if (owner == null) {
  223. child.setOwnerDocument (myOwner);
  224. }
  225. if (child.hasChildNodes ()) {
  226. for (int i = 0; true; i++) {
  227. Node node = child.item (i);
  228. if (node == null)
  229. break;
  230. if (node.getOwnerDocument () == null)
  231. ((NodeBase)node).setOwnerDocument (myOwner);
  232. else if (node.getOwnerDocument () != myOwner)
  233. throw new DomEx (DomEx.WRONG_DOCUMENT_ERR);
  234. }
  235. }
  236. return child;
  237. }
  238. // makes sure that child isn't an ancestor of this
  239. private void checkNotAncestor (Node newChild) throws DOMException
  240. {
  241. // text, etc ...
  242. if (!newChild.hasChildNodes ())
  243. return;
  244. Node ancestor = this;
  245. while (ancestor != null) {
  246. if (newChild == ancestor)
  247. throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
  248. ancestor = ancestor.getParentNode ();
  249. }
  250. }
  251. // update mutation count
  252. private void mutated ()
  253. {
  254. XmlDocument doc = ownerDocument;
  255. if (doc == null && this instanceof XmlDocument)
  256. doc = (XmlDocument) this;
  257. if (doc != null)
  258. doc.mutationCount++;
  259. }
  260. //
  261. // When fragments are appended/inserted/replaced, their entire
  262. // contents get moved and the fragment becomes empty.
  263. //
  264. private void consumeFragment (Node fragment, Node before)
  265. throws DOMException
  266. {
  267. ParentNode frag = (ParentNode) fragment;
  268. Node temp;
  269. // don't start insertions we can't complete
  270. for (int i = 0; (temp = frag.item (i)) != null; i++) {
  271. checkNotAncestor (temp);
  272. checkChildType (temp.getNodeType ());
  273. }
  274. while ((temp = frag.item (0)) != null)
  275. insertBefore (temp, before);
  276. }
  277. /**
  278. * <b>DOM:</b> Appends the child to the set of this node's children.
  279. * The new child must belong to this document.
  280. *
  281. * @param newChild the new child to be appended
  282. */
  283. public Node appendChild (Node newChild)
  284. throws DOMException
  285. {
  286. NodeBase child;
  287. if (readonly)
  288. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  289. child = checkDocument (newChild);
  290. if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
  291. consumeFragment (newChild, null);
  292. return newChild;
  293. }
  294. checkNotAncestor (newChild);
  295. checkChildType (child.getNodeType ());
  296. // this is the only place this vector needs allocating,
  297. // though it may also need to be grown in insertBefore.
  298. // most elements have very few children
  299. if (children == null)
  300. children = new NodeBase [3];
  301. else if (children.length == length) {
  302. NodeBase temp [] = new NodeBase [length * 2];
  303. System.arraycopy (children, 0, temp, 0, length);
  304. children = temp;
  305. }
  306. child.setParentNode (this, length);
  307. children [length++] = child;
  308. mutated ();
  309. return child;
  310. }
  311. /**
  312. * <b>DOM:</b> Inserts the new child before the specified child, which
  313. * if null indicates appending the new child to the current set of
  314. * children. The new child must belong to this particular document.
  315. * If the newChild is already in the tree, it is first removed.
  316. *
  317. * @param newChild the new child to be inserted
  318. * @param refChild node before which newChild is to be inserted
  319. */
  320. public Node insertBefore (Node newChild, Node refChild)
  321. throws DOMException
  322. {
  323. NodeBase child;
  324. if (readonly)
  325. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  326. if (refChild == null)
  327. return appendChild (newChild);
  328. if (length == 0)
  329. throw new DomEx (DomEx.NOT_FOUND_ERR);
  330. child = checkDocument (newChild);
  331. if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
  332. consumeFragment (newChild, refChild);
  333. return newChild;
  334. }
  335. checkNotAncestor (newChild);
  336. checkChildType (newChild.getNodeType ());
  337. // If the newChild is already in the tree, it is first removed
  338. for (int i = 0; i < length; i++) {
  339. if (children[i] == newChild) {
  340. removeChild(newChild);
  341. break;
  342. }
  343. }
  344. // grow array if needed
  345. if (children.length == length) {
  346. NodeBase temp [] = new NodeBase [length * 2];
  347. System.arraycopy (children, 0, temp, 0, length);
  348. children = temp;
  349. }
  350. for (int i = 0; i < length; i++) {
  351. if (children [i] != refChild)
  352. continue;
  353. child.setParentNode (this, i);
  354. System.arraycopy (children, i, children, i + 1, length - i);
  355. children [i] = child;
  356. length++;
  357. mutated ();
  358. return newChild;
  359. }
  360. throw new DomEx (DomEx.NOT_FOUND_ERR);
  361. }
  362. /**
  363. * <b>DOM:</b> Replaces the specified child with the new node,
  364. * returning the original child or throwing an exception. The new
  365. * child must belong to this particular document. If the newChild is
  366. * already in the tree, it is first removed.
  367. *
  368. * @param newChild the new child to be inserted
  369. * @param refChild node which is to be replaced
  370. */
  371. public Node replaceChild (Node newChild, Node refChild)
  372. throws DOMException
  373. {
  374. NodeBase child;
  375. if (readonly)
  376. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  377. if (newChild == null || refChild == null)
  378. throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR);
  379. if (children == null)
  380. throw new DomEx (DomEx.NOT_FOUND_ERR);
  381. child = checkDocument (newChild);
  382. if (newChild.getNodeType () == DOCUMENT_FRAGMENT_NODE) {
  383. consumeFragment (newChild, refChild);
  384. return removeChild (refChild);
  385. }
  386. checkNotAncestor (newChild);
  387. checkChildType (newChild.getNodeType ());
  388. // If the newChild is already in the tree, it is first removed
  389. for (int i = 0; i < length; i++) {
  390. if (children[i] == newChild) {
  391. removeChild(newChild);
  392. break;
  393. }
  394. }
  395. for (int i = 0; i < length; i++) {
  396. if (children [i] != refChild)
  397. continue;
  398. child.setParentNode (this, i);
  399. children [i] = child;
  400. ((NodeBase) refChild).setParentNode (null, -1);
  401. mutated ();
  402. return refChild;
  403. }
  404. throw new DomEx (DomEx.NOT_FOUND_ERR);
  405. }
  406. /**
  407. * <b>DOM:</b> removes child if present, returning argument.
  408. *
  409. * @param oldChild the node which is to be removed
  410. */
  411. public Node removeChild (Node oldChild)
  412. throws DOMException
  413. {
  414. NodeBase child;
  415. if (readonly)
  416. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  417. if (!(oldChild instanceof NodeBase))
  418. throw new DomEx (DomEx.NOT_FOUND_ERR);
  419. child = (NodeBase) oldChild;
  420. for (int i = 0; i < length; i++) {
  421. if (children [i] != child)
  422. continue;
  423. if ((i + 1) != length)
  424. System.arraycopy (children, i + 1, children, i,
  425. (length - 1) - i);
  426. length--;
  427. children [length] = null;
  428. child.setParentNode (null, -1);
  429. mutated ();
  430. return oldChild;
  431. }
  432. throw new DomEx (DomEx.NOT_FOUND_ERR);
  433. }
  434. /**
  435. * <b>DOM:</b> Returns a "live" list view of the elements below this
  436. * one which have the specified tag name. Because this is "live", this
  437. * API is dangerous -- indices are not stable in the face of most tree
  438. * updates. Use a TreeWalker instead.
  439. *
  440. * @param tagname the tag name to show; or "*" for all elements.
  441. * @return list of such elements
  442. */
  443. public NodeList getElementsByTagName (String tagname)
  444. {
  445. if ("*".equals (tagname))
  446. tagname = null;
  447. return new TagList (tagname);
  448. }
  449. /**
  450. * @since DOM Level 2
  451. */
  452. public NodeList getElementsByTagNameNS(String namespaceURI,
  453. String localName) {
  454. if ("*".equals(namespaceURI)) {
  455. namespaceURI = null;
  456. }
  457. if ("*".equals(localName)) {
  458. localName = null;
  459. }
  460. return new TagListNS(namespaceURI, localName);
  461. }
  462. //
  463. // Slightly optimized to track document mutation count. For now
  464. // we assume that a 32 bit counter won't wrap around, and that
  465. // there's no point in caching list length.
  466. //
  467. class TagList implements NodeList {
  468. protected String tag;
  469. protected int lastMutationCount;
  470. protected int lastIndex;
  471. protected TreeWalker lastWalker;
  472. protected int getLastMutationCount() {
  473. XmlDocument doc = (XmlDocument) getOwnerDocument ();
  474. return (doc == null) ? 0 : doc.mutationCount;
  475. }
  476. TagList (String tag) { this.tag = tag; }
  477. public Node item (int i)
  478. {
  479. if (i < 0)
  480. return null;
  481. int temp = getLastMutationCount ();
  482. // Can we try to reuse the last walker?
  483. if (lastWalker != null) {
  484. if (i < lastIndex || temp != lastMutationCount)
  485. lastWalker = null;
  486. }
  487. // if not, get a new one ...
  488. if (lastWalker == null) {
  489. lastWalker = new TreeWalker (ParentNode.this);
  490. lastIndex = -1;
  491. lastMutationCount = temp;
  492. }
  493. if (i == lastIndex)
  494. return lastWalker.getCurrent ();
  495. Node node = null;
  496. while (i > lastIndex
  497. && (node = lastWalker.getNextElement (tag)) != null) {
  498. lastIndex++;
  499. }
  500. // If we walk off the end of the list, throw away lastWalker
  501. if (node == null) {
  502. lastWalker = null;
  503. }
  504. return node;
  505. }
  506. public int getLength ()
  507. {
  508. TreeWalker walker = new TreeWalker (ParentNode.this);
  509. Node node = null;
  510. int retval;
  511. for (retval = 0;
  512. (node = walker.getNextElement (tag)) != null;
  513. retval++)
  514. continue;
  515. return retval;
  516. }
  517. }
  518. // Namespace version
  519. // XXX Ugly: much code in common with superclass
  520. class TagListNS extends TagList {
  521. private String namespaceURI;
  522. TagListNS(String namespaceURI, String localName) {
  523. // XXX Use the super.tag field as a localName
  524. super(localName);
  525. this.namespaceURI = namespaceURI;
  526. }
  527. public Node item(int i) {
  528. if (i < 0) {
  529. return null;
  530. }
  531. int temp = getLastMutationCount();
  532. // Can we try to reuse the last walker?
  533. if (lastWalker != null) {
  534. if (i < lastIndex || temp != lastMutationCount) {
  535. lastWalker = null;
  536. }
  537. }
  538. // if not, get a new one ...
  539. if (lastWalker == null) {
  540. lastWalker = new TreeWalker(ParentNode.this);
  541. lastIndex = -1;
  542. lastMutationCount = temp;
  543. }
  544. if (i == lastIndex) {
  545. return lastWalker.getCurrent();
  546. }
  547. Node node = null;
  548. while (i > lastIndex
  549. && (node = lastWalker.getNextElement(namespaceURI,
  550. tag)) != null) {
  551. lastIndex++;
  552. }
  553. // If we walk off the end of the list, throw away lastWalker
  554. if (node == null) {
  555. lastWalker = null;
  556. }
  557. return node;
  558. }
  559. public int getLength() {
  560. TreeWalker walker = new TreeWalker(ParentNode.this);
  561. int count;
  562. for (count = 0;
  563. walker.getNextElement(namespaceURI, tag) != null;
  564. count++) {
  565. // noop
  566. }
  567. return count;
  568. }
  569. }
  570. /**
  571. * Returns the index of the node in the list of children, such
  572. * that <em>item()</em> will return that child.
  573. *
  574. * @param maybeChild the node which may be a child of this one
  575. * @return the index of the node in the set of children, or
  576. * else -1 if that node is not a child
  577. */
  578. final public int getIndexOf (Node maybeChild)
  579. {
  580. for (int i = 0; i < length; i++)
  581. if (children [i] == maybeChild)
  582. return i;
  583. return -1;
  584. }
  585. /**
  586. * @since DOM Level 2
  587. * In DOM2, normalize() was generalized and got moved to Node.
  588. *
  589. * XXX Comments below are old:
  590. * <b>DOM2:</b> Merges all adjacent Text nodes in the tree rooted by this
  591. * element. Avoid using this on large blocks of text not separated
  592. * by markup such as elements or processing instructions, since it
  593. * can require arbitrarily large blocks of contiguous memory.
  594. *
  595. * XXX The following extension breaks a DOM conformance test so the
  596. * code has been modified to not behave as described:
  597. * <P> As a compatible extension to DOM, this normalizes treatment
  598. * of whitespace except when the <em>xml:space='preserve'</em>
  599. * attribute value applies to a node. All whitespace is normalized
  600. * to one space. This ensures that text which is pretty-printed and
  601. * then reread (and normalized) retains the same content. </P>
  602. */
  603. public void normalize() {
  604. boolean preserve = false;
  605. boolean knowPreserve = false;
  606. if (readonly) {
  607. throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR);
  608. }
  609. for (int i = 0; true; i++) {
  610. Node node = item(i);
  611. if (node == null) {
  612. break;
  613. }
  614. switch (node.getNodeType()) {
  615. case ELEMENT_NODE:
  616. ((Element)node).normalize ();
  617. continue;
  618. // case CDATA_SECTION_NODE:
  619. case TEXT_NODE: {
  620. Node node2 = item(i + 1);
  621. if (node2 == null || node2.getNodeType() != TEXT_NODE) {
  622. if (false) {
  623. // The following code breaks DOM conformance so this
  624. // feature is turned off.
  625. // See if xml:space='preserve' is set...
  626. if (!knowPreserve) {
  627. preserve = "preserve".equals(
  628. getInheritedAttribute("xml:space"));
  629. knowPreserve = true;
  630. }
  631. // ... and if not, normalize whitespace
  632. if (!preserve) {
  633. char[] buf = ((TextNode)node).data;
  634. // XXX this isn't supposed to happen
  635. if (buf == null || buf.length == 0) {
  636. removeChild(node);
  637. i--;
  638. continue;
  639. }
  640. int current = removeWhiteSpaces(buf);
  641. // compact if it shrank
  642. if (current != buf.length) {
  643. char[] tmp = new char[current];
  644. System.arraycopy(buf, 0, tmp, 0, current);
  645. ((TextNode)node).data = tmp;
  646. }
  647. }
  648. }
  649. continue;
  650. }
  651. ((TextNode) node).joinNextText();
  652. i--;
  653. continue;
  654. }
  655. default:
  656. continue;
  657. }
  658. }
  659. }
  660. /*
  661. * removes white leading, trailing and extra white spaces from the buffer.
  662. * returns the size of the new buf after the white spaces are removed.
  663. */
  664. public int removeWhiteSpaces(char[] buf) {
  665. int current = 0;
  666. int j = 0;
  667. // copy to beginning, normalizing whitespace
  668. // (including leading, trailing) to one space
  669. while (j < buf.length) {
  670. boolean sawSpace = false;
  671. char c = buf[j++];
  672. if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
  673. c = ' ';
  674. sawSpace = true;
  675. }
  676. buf[current++] = c;
  677. if (sawSpace) {
  678. while (j < buf.length) {
  679. c = buf[j];
  680. if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
  681. j++;
  682. continue;
  683. } else {
  684. break;
  685. }
  686. }
  687. }
  688. }
  689. return current;
  690. }
  691. }