You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FObj.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fo;
  19. import java.util.Collections;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.ListIterator;
  23. import java.util.Map;
  24. import java.util.NoSuchElementException;
  25. import java.util.Set;
  26. import org.xml.sax.Attributes;
  27. import org.xml.sax.Locator;
  28. import org.apache.xmlgraphics.util.QName;
  29. import org.apache.fop.apps.FOPException;
  30. import org.apache.fop.fo.extensions.ExtensionAttachment;
  31. import org.apache.fop.fo.flow.Marker;
  32. import org.apache.fop.fo.properties.PropertyMaker;
  33. /**
  34. * Base class for representation of formatting objects and their processing.
  35. */
  36. public abstract class FObj extends FONode implements Constants {
  37. /** the list of property makers */
  38. private static PropertyMaker[] propertyListTable
  39. = FOPropertyMapping.getGenericMappings();
  40. /**
  41. * pointer to the descendant subtree
  42. */
  43. protected FONode firstChild;
  44. /** The list of extension attachments, null if none */
  45. private List extensionAttachments = null;
  46. /** The map of foreign attributes, null if none */
  47. private Map foreignAttributes = null;
  48. /** Used to indicate if this FO is either an Out Of Line FO (see rec)
  49. * or a descendant of one. Used during FO validation.
  50. */
  51. private boolean isOutOfLineFODescendant = false;
  52. /** Markers added to this element. */
  53. private Map markers = null;
  54. // The value of properties relevant for all fo objects
  55. private String id = null;
  56. // End of property values
  57. /**
  58. * Create a new formatting object.
  59. * All formatting object classes extend this class.
  60. *
  61. * @param parent the parent node
  62. */
  63. public FObj(FONode parent) {
  64. super(parent);
  65. // determine if isOutOfLineFODescendant should be set
  66. if (parent != null && parent instanceof FObj) {
  67. if (((FObj) parent).getIsOutOfLineFODescendant()) {
  68. isOutOfLineFODescendant = true;
  69. } else {
  70. int foID = getNameId();
  71. if (foID == FO_FLOAT || foID == FO_FOOTNOTE
  72. || foID == FO_FOOTNOTE_BODY) {
  73. isOutOfLineFODescendant = true;
  74. }
  75. }
  76. }
  77. }
  78. /**
  79. * {@inheritDoc}
  80. */
  81. public FONode clone(FONode parent, boolean removeChildren)
  82. throws FOPException {
  83. FObj fobj = (FObj) super.clone(parent, removeChildren);
  84. if (removeChildren) {
  85. fobj.firstChild = null;
  86. }
  87. return fobj;
  88. }
  89. /**
  90. * Returns the PropertyMaker for a given property ID.
  91. * @param propId the property ID
  92. * @return the requested Property Maker
  93. */
  94. public static PropertyMaker getPropertyMakerFor(int propId) {
  95. return propertyListTable[propId];
  96. }
  97. /**
  98. * {@inheritDoc}
  99. */
  100. public void processNode(String elementName, Locator locator,
  101. Attributes attlist, PropertyList pList)
  102. throws FOPException {
  103. setLocator(locator);
  104. pList.addAttributesToList(attlist);
  105. if (!inMarker()
  106. || "marker".equals(elementName)) {
  107. pList.setWritingMode();
  108. bind(pList);
  109. }
  110. }
  111. /**
  112. * Create a default property list for this element.
  113. * {@inheritDoc}
  114. */
  115. protected PropertyList createPropertyList(PropertyList parent,
  116. FOEventHandler foEventHandler) throws FOPException {
  117. return foEventHandler.getPropertyListMaker().make(this, parent);
  118. }
  119. /**
  120. * Bind property values from the property list to the FO node.
  121. * Must be overridden in all FObj subclasses that have properties
  122. * applying to it.
  123. * @param pList the PropertyList where the properties can be found.
  124. * @throws FOPException if there is a problem binding the values
  125. */
  126. public void bind(PropertyList pList) throws FOPException {
  127. id = pList.get(PR_ID).getString();
  128. }
  129. /**
  130. * {@inheritDoc}
  131. * @throws FOPException FOP Exception
  132. */
  133. protected void startOfNode() throws FOPException {
  134. if (id != null) {
  135. checkId(id);
  136. }
  137. }
  138. /**
  139. * Setup the id for this formatting object.
  140. * Most formatting objects can have an id that can be referenced.
  141. * This methods checks that the id isn't already used by another FO
  142. *
  143. * @param id the id to check
  144. * @throws ValidationException if the ID is already defined elsewhere
  145. * (strict validation only)
  146. */
  147. private void checkId(String id) throws ValidationException {
  148. if (!inMarker() && !id.equals("")) {
  149. Set idrefs = getFOEventHandler().getIDReferences();
  150. if (!idrefs.contains(id)) {
  151. idrefs.add(id);
  152. } else {
  153. getFOValidationEventProducer().idNotUnique(this, getName(), id, true, locator);
  154. }
  155. }
  156. }
  157. /**
  158. * Returns Out Of Line FO Descendant indicator.
  159. * @return true if Out of Line FO or Out Of Line descendant, false otherwise
  160. */
  161. public boolean getIsOutOfLineFODescendant() {
  162. return isOutOfLineFODescendant;
  163. }
  164. /**
  165. * {@inheritDoc}
  166. */
  167. protected void addChildNode(FONode child) throws FOPException {
  168. if (canHaveMarkers() && child.getNameId() == FO_MARKER) {
  169. addMarker((Marker) child);
  170. } else {
  171. ExtensionAttachment attachment = child.getExtensionAttachment();
  172. if (attachment != null) {
  173. /* This removes the element from the normal children,
  174. * so no layout manager is being created for them
  175. * as they are only additional information.
  176. */
  177. addExtensionAttachment(attachment);
  178. } else {
  179. if (firstChild == null) {
  180. firstChild = child;
  181. } else {
  182. FONode prevChild = firstChild;
  183. while (prevChild.siblings != null
  184. && prevChild.siblings[1] != null) {
  185. prevChild = prevChild.siblings[1];
  186. }
  187. FONode.attachSiblings(prevChild, child);
  188. }
  189. }
  190. }
  191. }
  192. /**
  193. * Used by RetrieveMarker during Marker-subtree cloning
  194. * @param child the (cloned) child node
  195. * @param parent the (cloned) parent node
  196. * @throws FOPException when the child could not be added to the parent
  197. */
  198. protected static void addChildTo(FONode child, FObj parent)
  199. throws FOPException {
  200. parent.addChildNode(child);
  201. }
  202. /** {@inheritDoc} */
  203. public void removeChild(FONode child) {
  204. FONode nextChild = null;
  205. if (child.siblings != null) {
  206. nextChild = child.siblings[1];
  207. }
  208. if (child == firstChild) {
  209. firstChild = nextChild;
  210. if (firstChild != null) {
  211. firstChild.siblings[0] = null;
  212. }
  213. } else {
  214. FONode prevChild = child.siblings[0];
  215. prevChild.siblings[1] = nextChild;
  216. if (nextChild != null) {
  217. nextChild.siblings[0] = prevChild;
  218. }
  219. }
  220. }
  221. /**
  222. * Find the nearest parent, grandparent, etc. FONode that is also an FObj
  223. * @return FObj the nearest ancestor FONode that is an FObj
  224. */
  225. public FObj findNearestAncestorFObj() {
  226. FONode par = parent;
  227. while (par != null && !(par instanceof FObj)) {
  228. par = par.parent;
  229. }
  230. return (FObj) par;
  231. }
  232. /**
  233. * Check if this formatting object generates reference areas.
  234. * @return true if generates reference areas
  235. * @todo see if needed
  236. */
  237. public boolean generatesReferenceAreas() {
  238. return false;
  239. }
  240. /** {@inheritDoc} */
  241. public FONodeIterator getChildNodes() {
  242. if (hasChildren()) {
  243. return new FObjIterator(this);
  244. }
  245. return null;
  246. }
  247. /**
  248. * Indicates whether this formatting object has children.
  249. * @return true if there are children
  250. */
  251. public boolean hasChildren() {
  252. return this.firstChild != null;
  253. }
  254. /**
  255. * Return an iterator over the object's childNodes starting
  256. * at the passed-in node (= first call to iterator.next() will
  257. * return childNode)
  258. * @param childNode First node in the iterator
  259. * @return A ListIterator or null if childNode isn't a child of
  260. * this FObj.
  261. */
  262. public FONodeIterator getChildNodes(FONode childNode) {
  263. FONodeIterator it = getChildNodes();
  264. if (it != null) {
  265. if (firstChild == childNode) {
  266. return it;
  267. } else {
  268. while (it.hasNext()
  269. && it.nextNode().siblings[1] != childNode) {
  270. //nop
  271. }
  272. if (it.hasNext()) {
  273. return it;
  274. } else {
  275. return null;
  276. }
  277. }
  278. }
  279. return null;
  280. }
  281. /**
  282. * Notifies a FObj that one of it's children is removed.
  283. * This method is subclassed by Block to clear the
  284. * firstInlineChild variable in case it doesn't generate
  285. * any areas (see addMarker()).
  286. * @param node the node that was removed
  287. */
  288. protected void notifyChildRemoval(FONode node) {
  289. //nop
  290. }
  291. /**
  292. * Add the marker to this formatting object.
  293. * If this object can contain markers it checks that the marker
  294. * has a unique class-name for this object and that it is
  295. * the first child.
  296. * @param marker Marker to add.
  297. */
  298. protected void addMarker(Marker marker) {
  299. String mcname = marker.getMarkerClassName();
  300. if (firstChild != null) {
  301. // check for empty childNodes
  302. for (Iterator iter = getChildNodes(); iter.hasNext();) {
  303. FONode node = (FONode) iter.next();
  304. if (node instanceof FObj
  305. || (node instanceof FOText
  306. && ((FOText) node).willCreateArea())) {
  307. getFOValidationEventProducer().markerNotInitialChild(this, getName(),
  308. mcname, locator);
  309. return;
  310. } else if (node instanceof FOText) {
  311. iter.remove();
  312. notifyChildRemoval(node);
  313. }
  314. }
  315. }
  316. if (markers == null) {
  317. markers = new java.util.HashMap();
  318. }
  319. if (!markers.containsKey(mcname)) {
  320. markers.put(mcname, marker);
  321. } else {
  322. getFOValidationEventProducer().markerNotUniqueForSameParent(this, getName(),
  323. mcname, locator);
  324. }
  325. }
  326. /**
  327. * @return true if there are any Markers attached to this object
  328. */
  329. public boolean hasMarkers() {
  330. return markers != null && !markers.isEmpty();
  331. }
  332. /**
  333. * @return the collection of Markers attached to this object
  334. */
  335. public Map getMarkers() {
  336. return markers;
  337. }
  338. /** {@inheritDoc} */
  339. protected String getContextInfoAlt() {
  340. StringBuffer sb = new StringBuffer();
  341. if (getLocalName() != null) {
  342. sb.append(getName());
  343. sb.append(", ");
  344. }
  345. if (hasId()) {
  346. sb.append("id=").append(getId());
  347. return sb.toString();
  348. }
  349. String s = gatherContextInfo();
  350. if (s != null) {
  351. sb.append("\"");
  352. if (s.length() < 32) {
  353. sb.append(s);
  354. } else {
  355. sb.append(s.substring(0, 32));
  356. sb.append("...");
  357. }
  358. sb.append("\"");
  359. return sb.toString();
  360. } else {
  361. return null;
  362. }
  363. }
  364. /** {@inheritDoc} */
  365. protected String gatherContextInfo() {
  366. if (getLocator() != null) {
  367. return super.gatherContextInfo();
  368. } else {
  369. ListIterator iter = getChildNodes();
  370. if (iter == null) {
  371. return null;
  372. }
  373. StringBuffer sb = new StringBuffer();
  374. while (iter.hasNext()) {
  375. FONode node = (FONode) iter.next();
  376. String s = node.gatherContextInfo();
  377. if (s != null) {
  378. if (sb.length() > 0) {
  379. sb.append(", ");
  380. }
  381. sb.append(s);
  382. }
  383. }
  384. return (sb.length() > 0 ? sb.toString() : null);
  385. }
  386. }
  387. /**
  388. * Convenience method for validity checking. Checks if the
  389. * incoming node is a member of the "%block;" parameter entity
  390. * as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
  391. * @param nsURI namespace URI of incoming node
  392. * @param lName local name (i.e., no prefix) of incoming node
  393. * @return true if a member, false if not
  394. */
  395. protected boolean isBlockItem(String nsURI, String lName) {
  396. return (FO_URI.equals(nsURI)
  397. && (lName.equals("block")
  398. || lName.equals("table")
  399. || lName.equals("table-and-caption")
  400. || lName.equals("block-container")
  401. || lName.equals("list-block")
  402. || lName.equals("float")
  403. || isNeutralItem(nsURI, lName)));
  404. }
  405. /**
  406. * Convenience method for validity checking. Checks if the
  407. * incoming node is a member of the "%inline;" parameter entity
  408. * as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
  409. * @param nsURI namespace URI of incoming node
  410. * @param lName local name (i.e., no prefix) of incoming node
  411. * @return true if a member, false if not
  412. */
  413. protected boolean isInlineItem(String nsURI, String lName) {
  414. return (FO_URI.equals(nsURI)
  415. && (lName.equals("bidi-override")
  416. || lName.equals("character")
  417. || lName.equals("external-graphic")
  418. || lName.equals("instream-foreign-object")
  419. || lName.equals("inline")
  420. || lName.equals("inline-container")
  421. || lName.equals("leader")
  422. || lName.equals("page-number")
  423. || lName.equals("page-number-citation")
  424. || lName.equals("page-number-citation-last")
  425. || lName.equals("basic-link")
  426. || (lName.equals("multi-toggle")
  427. && (getNameId() == FO_MULTI_CASE
  428. || findAncestor(FO_MULTI_CASE) > 0))
  429. || (lName.equals("footnote")
  430. && !isOutOfLineFODescendant)
  431. || isNeutralItem(nsURI, lName)));
  432. }
  433. /**
  434. * Convenience method for validity checking. Checks if the
  435. * incoming node is a member of the "%block;" parameter entity
  436. * or "%inline;" parameter entity
  437. * @param nsURI namespace URI of incoming node
  438. * @param lName local name (i.e., no prefix) of incoming node
  439. * @return true if a member, false if not
  440. */
  441. protected boolean isBlockOrInlineItem(String nsURI, String lName) {
  442. return (isBlockItem(nsURI, lName) || isInlineItem(nsURI, lName));
  443. }
  444. /**
  445. * Convenience method for validity checking. Checks if the
  446. * incoming node is a member of the neutral item list
  447. * as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
  448. * @param nsURI namespace URI of incoming node
  449. * @param lName local name (i.e., no prefix) of incoming node
  450. * @return true if a member, false if not
  451. */
  452. protected boolean isNeutralItem(String nsURI, String lName) {
  453. return (FO_URI.equals(nsURI)
  454. && (lName.equals("multi-switch")
  455. || lName.equals("multi-properties")
  456. || lName.equals("wrapper")
  457. || (!isOutOfLineFODescendant && lName.equals("float"))
  458. || lName.equals("retrieve-marker")));
  459. }
  460. /**
  461. * Convenience method for validity checking. Checks if the
  462. * current node has an ancestor of a given name.
  463. * @param ancestorID ID of node name to check for (e.g., FO_ROOT)
  464. * @return number of levels above FO where ancestor exists,
  465. * -1 if not found
  466. */
  467. protected int findAncestor(int ancestorID) {
  468. int found = 1;
  469. FONode temp = getParent();
  470. while (temp != null) {
  471. if (temp.getNameId() == ancestorID) {
  472. return found;
  473. }
  474. found += 1;
  475. temp = temp.getParent();
  476. }
  477. return -1;
  478. }
  479. /** @return the "id" property. */
  480. public String getId() {
  481. return id;
  482. }
  483. /** @return whether this object has an id set */
  484. public boolean hasId() {
  485. return id != null && id.length() > 0;
  486. }
  487. /** {@inheritDoc} */
  488. public String getNamespaceURI() {
  489. return FOElementMapping.URI;
  490. }
  491. /** {@inheritDoc} */
  492. public String getNormalNamespacePrefix() {
  493. return "fo";
  494. }
  495. /**
  496. * Add a new extension attachment to this FObj.
  497. * (see org.apache.fop.fo.FONode for details)
  498. *
  499. * @param attachment the attachment to add.
  500. */
  501. public void addExtensionAttachment(ExtensionAttachment attachment) {
  502. if (attachment == null) {
  503. throw new NullPointerException(
  504. "Parameter attachment must not be null");
  505. }
  506. if (extensionAttachments == null) {
  507. extensionAttachments = new java.util.ArrayList();
  508. }
  509. if (log.isDebugEnabled()) {
  510. log.debug("ExtensionAttachment of category "
  511. + attachment.getCategory() + " added to "
  512. + getName() + ": " + attachment);
  513. }
  514. extensionAttachments.add(attachment);
  515. }
  516. /** @return the extension attachments of this FObj. */
  517. public List getExtensionAttachments() {
  518. if (extensionAttachments == null) {
  519. return Collections.EMPTY_LIST;
  520. } else {
  521. return extensionAttachments;
  522. }
  523. }
  524. /**
  525. * Adds a foreign attribute to this FObj.
  526. * @param attributeName the attribute name as a QName instance
  527. * @param value the attribute value
  528. */
  529. public void addForeignAttribute(QName attributeName, String value) {
  530. /* TODO: Handle this over FOP's property mechanism so we can use
  531. * inheritance.
  532. */
  533. if (attributeName == null) {
  534. throw new NullPointerException("Parameter attributeName must not be null");
  535. }
  536. if (foreignAttributes == null) {
  537. foreignAttributes = new java.util.HashMap();
  538. }
  539. foreignAttributes.put(attributeName, value);
  540. }
  541. /** @return the map of foreign attributes */
  542. public Map getForeignAttributes() {
  543. if (foreignAttributes == null) {
  544. return Collections.EMPTY_MAP;
  545. } else {
  546. return foreignAttributes;
  547. }
  548. }
  549. /** {@inheritDoc} */
  550. public String toString() {
  551. return (super.toString() + "[@id=" + this.id + "]");
  552. }
  553. public class FObjIterator implements FONodeIterator {
  554. private static final int F_NONE_ALLOWED = 0;
  555. private static final int F_SET_ALLOWED = 1;
  556. private static final int F_REMOVE_ALLOWED = 2;
  557. private FONode currentNode;
  558. private FObj parentNode;
  559. private int currentIndex;
  560. private int flags = F_NONE_ALLOWED;
  561. protected FObjIterator(FObj parent) {
  562. this.parentNode = parent;
  563. this.currentNode = parent.firstChild;
  564. this.currentIndex = 0;
  565. this.flags = F_NONE_ALLOWED;
  566. }
  567. /**
  568. * {@inheritDoc}
  569. */
  570. public FObj parentNode() {
  571. return parentNode;
  572. }
  573. /**
  574. * {@inheritDoc}
  575. */
  576. public Object next() {
  577. if (currentNode != null) {
  578. if (currentIndex != 0) {
  579. if (currentNode.siblings != null
  580. && currentNode.siblings[1] != null) {
  581. currentNode = currentNode.siblings[1];
  582. } else {
  583. throw new NoSuchElementException();
  584. }
  585. }
  586. currentIndex++;
  587. flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED);
  588. return currentNode;
  589. } else {
  590. throw new NoSuchElementException();
  591. }
  592. }
  593. /**
  594. * {@inheritDoc}
  595. */
  596. public Object previous() {
  597. if (currentNode.siblings != null
  598. && currentNode.siblings[0] != null) {
  599. currentIndex--;
  600. currentNode = currentNode.siblings[0];
  601. flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED);
  602. return currentNode;
  603. } else {
  604. throw new NoSuchElementException();
  605. }
  606. }
  607. /**
  608. * {@inheritDoc}
  609. */
  610. public void set(Object o) {
  611. if ((flags & F_SET_ALLOWED) == F_SET_ALLOWED) {
  612. FONode newNode = (FONode) o;
  613. if (currentNode == parentNode.firstChild) {
  614. parentNode.firstChild = newNode;
  615. } else {
  616. FONode.attachSiblings(currentNode.siblings[0], newNode);
  617. }
  618. if (currentNode.siblings != null
  619. && currentNode.siblings[1] != null) {
  620. FONode.attachSiblings(newNode, currentNode.siblings[1]);
  621. }
  622. } else {
  623. throw new IllegalStateException();
  624. }
  625. }
  626. /**
  627. * {@inheritDoc}
  628. */
  629. public void add(Object o) {
  630. FONode newNode = (FONode) o;
  631. if (currentIndex == -1) {
  632. if (currentNode != null) {
  633. FONode.attachSiblings(newNode, currentNode);
  634. }
  635. parentNode.firstChild = newNode;
  636. currentIndex = 0;
  637. currentNode = newNode;
  638. } else {
  639. if (currentNode.siblings != null
  640. && currentNode.siblings[1] != null) {
  641. FONode.attachSiblings((FONode) o, currentNode.siblings[1]);
  642. }
  643. FONode.attachSiblings(currentNode, (FONode) o);
  644. }
  645. flags &= F_NONE_ALLOWED;
  646. }
  647. /**
  648. * {@inheritDoc}
  649. */
  650. public boolean hasNext() {
  651. return (currentNode != null)
  652. && ((currentIndex == 0)
  653. || (currentNode.siblings != null
  654. && currentNode.siblings[1] != null));
  655. }
  656. /**
  657. * {@inheritDoc}
  658. */
  659. public boolean hasPrevious() {
  660. return (currentIndex != 0)
  661. || (currentNode.siblings != null
  662. && currentNode.siblings[0] != null);
  663. }
  664. /**
  665. * {@inheritDoc}
  666. */
  667. public int nextIndex() {
  668. return currentIndex + 1;
  669. }
  670. /**
  671. * {@inheritDoc}
  672. */
  673. public int previousIndex() {
  674. return currentIndex - 1;
  675. }
  676. /**
  677. * {@inheritDoc}
  678. */
  679. public void remove() {
  680. if ((flags & F_REMOVE_ALLOWED) == F_REMOVE_ALLOWED) {
  681. parentNode.removeChild(currentNode);
  682. if (currentIndex == 0) {
  683. //first node removed
  684. currentNode = parentNode.firstChild;
  685. } else if (currentNode.siblings != null
  686. && currentNode.siblings[0] != null) {
  687. currentNode = currentNode.siblings[0];
  688. currentIndex--;
  689. } else {
  690. currentNode = null;
  691. }
  692. flags &= F_NONE_ALLOWED;
  693. } else {
  694. throw new IllegalStateException();
  695. }
  696. }
  697. /**
  698. * {@inheritDoc}
  699. */
  700. public FONode lastNode() {
  701. while (currentNode != null
  702. && currentNode.siblings != null
  703. && currentNode.siblings[1] != null) {
  704. currentNode = currentNode.siblings[1];
  705. currentIndex++;
  706. }
  707. return currentNode;
  708. }
  709. /**
  710. * {@inheritDoc}
  711. */
  712. public FONode firstNode() {
  713. currentNode = parentNode.firstChild;
  714. currentIndex = 0;
  715. return currentNode;
  716. }
  717. /**
  718. * {@inheritDoc}
  719. */
  720. public FONode nextNode() {
  721. return (FONode) next();
  722. }
  723. /**
  724. * {@inheritDoc}
  725. */
  726. public FONode previousNode() {
  727. return (FONode) previous();
  728. }
  729. }
  730. }