/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: FObj.java 1762060 2016-09-23 12:57:46Z ssteiner $ */

package org.apache.fop.fo;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;

import org.xml.sax.Attributes;
import org.xml.sax.Locator;

import org.apache.xmlgraphics.util.QName;

import org.apache.fop.apps.FOPException;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.properties.Property;
import org.apache.fop.fo.properties.PropertyMaker;

Base class for representation of formatting objects and their processing. All standard formatting object classes extend this class.
/** * Base class for representation of formatting objects and their processing. * All standard formatting object classes extend this class. */
public abstract class FObj extends FONode implements Constants {
the list of property makers
/** the list of property makers */
private static final PropertyMaker[] PROPERTY_LIST_TABLE = FOPropertyMapping.getGenericMappings();
pointer to the descendant subtree
/** pointer to the descendant subtree */
protected FONode firstChild;
pointer to the end of the descendant subtree
/** pointer to the end of the descendant subtree */
protected FONode lastChild;
The list of extension attachments, null if none
/** The list of extension attachments, null if none */
private List<ExtensionAttachment> extensionAttachments;
The map of foreign attributes, null if none
/** The map of foreign attributes, null if none */
private Map<QName, String> foreignAttributes;
Used to indicate if this FO is either an Out Of Line FO (see rec) or a descendant of one. Used during FO validation.
/** Used to indicate if this FO is either an Out Of Line FO (see rec) * or a descendant of one. Used during FO validation. */
private boolean isOutOfLineFODescendant;
Markers added to this element.
/** Markers added to this element. */
private Map<String, Marker> markers; private int bidiLevel = -1; // The value of properties relevant for all fo objects private String id; private String layer; // End of property values
Create a new formatting object.
Params:
  • parent – the parent node
/** * Create a new formatting object. * * @param parent the parent node */
public FObj(FONode parent) { super(parent); // determine if isOutOfLineFODescendant should be set if (parent != null && parent instanceof FObj) { if (((FObj) parent).getIsOutOfLineFODescendant()) { isOutOfLineFODescendant = true; } else { int foID = getNameId(); if (foID == FO_FLOAT || foID == FO_FOOTNOTE || foID == FO_FOOTNOTE_BODY) { isOutOfLineFODescendant = true; } } } }
{@inheritDoc}
/** {@inheritDoc} */
public FONode clone(FONode parent, boolean removeChildren) throws FOPException { FObj fobj = (FObj) super.clone(parent, removeChildren); if (removeChildren) { fobj.firstChild = null; } return fobj; }
Returns the PropertyMaker for a given property ID.
Params:
  • propId – the property ID
Returns:the requested Property Maker
/** * Returns the PropertyMaker for a given property ID. * @param propId the property ID * @return the requested Property Maker */
public static PropertyMaker getPropertyMakerFor(int propId) { return PROPERTY_LIST_TABLE[propId]; }
{@inheritDoc}
/** {@inheritDoc} */
public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList pList) throws FOPException { setLocator(locator); pList.addAttributesToList(attlist); if (!inMarker() || "marker".equals(elementName)) { bind(pList); } warnOnUnknownProperties(attlist, elementName, pList); } private void warnOnUnknownProperties(Attributes attlist, String objName, PropertyList propertyList) throws FOPException { Map<String, Property> unknowns = propertyList.getUnknownPropertyValues(); for (Entry<String, Property> entry : unknowns.entrySet()) { FOValidationEventProducer producer = FOValidationEventProducer.Provider.get(getUserAgent() .getEventBroadcaster()); producer.warnOnInvalidPropertyValue(this, objName, getAttributeNameForValue(attlist, entry.getValue(), propertyList), entry.getKey(), null, getLocator()); } } private String getAttributeNameForValue(Attributes attList, Property value, PropertyList propertyList) throws FOPException { for (int i = 0; i < attList.getLength(); i++) { String attributeName = attList.getQName(i); String attributeValue = attList.getValue(i); Property prop = propertyList.getPropertyForAttribute(attList, attributeName, attributeValue); if (prop != null && prop.equals(value)) { return attributeName; } } return "unknown"; }
Create a default property list for this element. {@inheritDoc}
/** * Create a default property list for this element. * {@inheritDoc} */
protected PropertyList createPropertyList(PropertyList parent, FOEventHandler foEventHandler) throws FOPException { return getBuilderContext().getPropertyListMaker().make(this, parent); }
Bind property values from the property list to the FO node. Must be overridden in all FObj subclasses that have properties applying to it.
Params:
  • pList – the PropertyList where the properties can be found.
Throws:
/** * Bind property values from the property list to the FO node. * Must be overridden in all FObj subclasses that have properties * applying to it. * @param pList the PropertyList where the properties can be found. * @throws FOPException if there is a problem binding the values */
public void bind(PropertyList pList) throws FOPException { id = pList.get(PR_ID).getString(); layer = pList.get(PR_X_LAYER).getString(); }
{@inheritDoc}
Throws:
/** * {@inheritDoc} * @throws FOPException FOP Exception */
public void startOfNode() throws FOPException { if (id != null) { checkId(id); } }
Setup the id for this formatting object. Most formatting objects can have an id that can be referenced. This methods checks that the id isn't already used by another FO
Params:
  • id – the id to check
Throws:
/** * Setup the id for this formatting object. * Most formatting objects can have an id that can be referenced. * This methods checks that the id isn't already used by another FO * * @param id the id to check * @throws ValidationException if the ID is already defined elsewhere * (strict validation only) */
private void checkId(String id) throws ValidationException { if (!inMarker() && !id.equals("")) { Set<String> idrefs = getBuilderContext().getIDReferences(); if (!idrefs.contains(id)) { idrefs.add(id); } else { getFOValidationEventProducer().idNotUnique(this, getName(), id, true, locator); } } }
Returns Out Of Line FO Descendant indicator.
Returns:true if Out of Line FO or Out Of Line descendant, false otherwise
/** * Returns Out Of Line FO Descendant indicator. * @return true if Out of Line FO or Out Of Line descendant, false otherwise */
boolean getIsOutOfLineFODescendant() { return isOutOfLineFODescendant; }
{@inheritDoc}
/** {@inheritDoc}*/
protected void addChildNode(FONode child) throws FOPException { if (child.getNameId() == FO_MARKER) { addMarker((Marker) child); } else { ExtensionAttachment attachment = child.getExtensionAttachment(); if (attachment != null) { /* This removes the element from the normal children, * so no layout manager is being created for them * as they are only additional information. */ addExtensionAttachment(attachment); } else { if (firstChild == null) { firstChild = child; lastChild = child; } else { if (lastChild == null) { FONode prevChild = firstChild; while (prevChild.siblings != null && prevChild.siblings[1] != null) { prevChild = prevChild.siblings[1]; } FONode.attachSiblings(prevChild, child); } else { FONode.attachSiblings(lastChild, child); lastChild = child; } } } } }
Used by RetrieveMarker during Marker-subtree cloning
Params:
  • child – the (cloned) child node
  • parent – the (cloned) parent node
Throws:
  • FOPException – when the child could not be added to the parent
/** * Used by RetrieveMarker during Marker-subtree cloning * @param child the (cloned) child node * @param parent the (cloned) parent node * @throws FOPException when the child could not be added to the parent */
protected static void addChildTo(FONode child, FONode parent) throws FOPException { parent.addChildNode(child); }
{@inheritDoc}
/** {@inheritDoc} */
public void removeChild(FONode child) { FONode nextChild = null; if (child.siblings != null) { nextChild = child.siblings[1]; } if (child == firstChild) { firstChild = nextChild; if (firstChild != null) { firstChild.siblings[0] = null; } } else if (child.siblings != null) { FONode prevChild = child.siblings[0]; prevChild.siblings[1] = nextChild; if (nextChild != null) { nextChild.siblings[0] = prevChild; } } if (child == lastChild) { if (child.siblings != null) { lastChild = siblings[0]; } else { lastChild = null; } } }
Find the nearest parent, grandparent, etc. FONode that is also an FObj
Returns:FObj the nearest ancestor FONode that is an FObj
/** * Find the nearest parent, grandparent, etc. FONode that is also an FObj * @return FObj the nearest ancestor FONode that is an FObj */
public FObj findNearestAncestorFObj() { FONode par = parent; while (par != null && !(par instanceof FObj)) { par = par.parent; } return (FObj) par; }
Check if this formatting object generates reference areas.
Returns:true if generates reference areas TODO see if needed
/** * Check if this formatting object generates reference areas. * @return true if generates reference areas * TODO see if needed */
public boolean generatesReferenceAreas() { return false; }
{@inheritDoc}
/** {@inheritDoc} */
public FONodeIterator getChildNodes() { if (hasChildren()) { return new FObjIterator(this); } return null; }
Indicates whether this formatting object has children.
Returns:true if there are children
/** * Indicates whether this formatting object has children. * @return true if there are children */
public boolean hasChildren() { return this.firstChild != null; }
Return an iterator over the object's childNodes starting at the passed-in node (= first call to iterator.next() will return childNode)
Params:
  • childNode – First node in the iterator
Returns:A FONodeIterator or null if childNode isn't a child of this FObj.
/** * Return an iterator over the object's childNodes starting * at the passed-in node (= first call to iterator.next() will * return childNode) * @param childNode First node in the iterator * @return A FONodeIterator or null if childNode isn't a child of * this FObj. */
public FONodeIterator getChildNodes(FONode childNode) { FONodeIterator it = getChildNodes(); if (it != null) { if (firstChild == childNode) { return it; } else { while (it.hasNext() && it.next().siblings[1] != childNode) { //nop } if (it.hasNext()) { return it; } else { return null; } } } return null; }
Notifies a FObj that one of it's children is removed. This method is subclassed by Block to clear the firstInlineChild variable in case it doesn't generate any areas (see addMarker()).
Params:
  • node – the node that was removed
/** * Notifies a FObj that one of it's children is removed. * This method is subclassed by Block to clear the * firstInlineChild variable in case it doesn't generate * any areas (see addMarker()). * @param node the node that was removed */
void notifyChildRemoval(FONode node) { //nop }
Add the marker to this formatting object. If this object can contain markers it checks that the marker has a unique class-name for this object and that it is the first child.
Params:
  • marker – Marker to add.
/** * Add the marker to this formatting object. * If this object can contain markers it checks that the marker * has a unique class-name for this object and that it is * the first child. * @param marker Marker to add. */
protected void addMarker(Marker marker) { String mcname = marker.getMarkerClassName(); if (firstChild != null) { // check for empty childNodes for (FONodeIterator iter = getChildNodes(); iter.hasNext();) { FONode node = iter.next(); if (node instanceof FObj || (node instanceof FOText && ((FOText) node).willCreateArea())) { getFOValidationEventProducer().markerNotInitialChild(this, getName(), mcname, locator); return; } else if (node instanceof FOText) { iter.remove(); notifyChildRemoval(node); } } } if (markers == null) { markers = new HashMap<String, Marker>(); } if (!markers.containsKey(mcname)) { markers.put(mcname, marker); } else { getFOValidationEventProducer().markerNotUniqueForSameParent(this, getName(), mcname, locator); } }
Returns:true if there are any Markers attached to this object
/** * @return true if there are any Markers attached to this object */
public boolean hasMarkers() { return markers != null && !markers.isEmpty(); }
Returns:the collection of Markers attached to this object
/** * @return the collection of Markers attached to this object */
public Map<String, Marker> getMarkers() { return markers; }
{@inheritDoc}
/** {@inheritDoc} */
protected String getContextInfoAlt() { StringBuilder sb = new StringBuilder(); if (getLocalName() != null) { sb.append(getName()); sb.append(", "); } if (hasId()) { sb.append("id=").append(getId()); return sb.toString(); } String s = gatherContextInfo(); if (s != null) { sb.append("\""); if (s.length() < 32) { sb.append(s); } else { sb.append(s.substring(0, 32)); sb.append("..."); } sb.append("\""); return sb.toString(); } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
protected String gatherContextInfo() { if (getLocator() != null) { return super.gatherContextInfo(); } else { FONodeIterator iter = getChildNodes(); if (iter == null) { return null; } StringBuilder sb = new StringBuilder(); while (iter.hasNext()) { FONode node = iter.next(); String s = node.gatherContextInfo(); if (s != null) { if (sb.length() > 0) { sb.append(", "); } sb.append(s); } } return (sb.length() > 0 ? sb.toString() : null); } }
Convenience method for validity checking. Checks if the incoming node is a member of the "%block;" parameter entity as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
Params:
  • nsURI – namespace URI of incoming node
  • lName – local name (i.e., no prefix) of incoming node
Returns:true if a member, false if not
/** * Convenience method for validity checking. Checks if the * incoming node is a member of the "%block;" parameter entity * as defined in Sect. 6.2 of the XSL 1.0 &amp; 1.1 Recommendations * * @param nsURI namespace URI of incoming node * @param lName local name (i.e., no prefix) of incoming node * @return true if a member, false if not */
protected boolean isBlockItem(String nsURI, String lName) { return (FO_URI.equals(nsURI) && ("block".equals(lName) || "table".equals(lName) || "table-and-caption".equals(lName) || "block-container".equals(lName) || "list-block".equals(lName) || "float".equals(lName) || isNeutralItem(nsURI, lName))); }
Convenience method for validity checking. Checks if the incoming node is a member of the "%inline;" parameter entity as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
Params:
  • nsURI – namespace URI of incoming node
  • lName – local name (i.e., no prefix) of incoming node
Returns:true if a member, false if not
/** * Convenience method for validity checking. Checks if the * incoming node is a member of the "%inline;" parameter entity * as defined in Sect. 6.2 of the XSL 1.0 &amp; 1.1 Recommendations * * @param nsURI namespace URI of incoming node * @param lName local name (i.e., no prefix) of incoming node * @return true if a member, false if not */
protected boolean isInlineItem(String nsURI, String lName) { return (FO_URI.equals(nsURI) && ("bidi-override".equals(lName) || "character".equals(lName) || "external-graphic".equals(lName) || "instream-foreign-object".equals(lName) || "inline".equals(lName) || "inline-container".equals(lName) || "leader".equals(lName) || "page-number".equals(lName) || "page-number-citation".equals(lName) || "page-number-citation-last".equals(lName) || "basic-link".equals(lName) || ("multi-toggle".equals(lName) && (getNameId() == FO_MULTI_CASE || findAncestor(FO_MULTI_CASE) > 0)) || ("footnote".equals(lName) && !isOutOfLineFODescendant) || isNeutralItem(nsURI, lName))); }
Convenience method for validity checking. Checks if the incoming node is a member of the "%block;" parameter entity or "%inline;" parameter entity
Params:
  • nsURI – namespace URI of incoming node
  • lName – local name (i.e., no prefix) of incoming node
Returns:true if a member, false if not
/** * Convenience method for validity checking. Checks if the * incoming node is a member of the "%block;" parameter entity * or "%inline;" parameter entity * @param nsURI namespace URI of incoming node * @param lName local name (i.e., no prefix) of incoming node * @return true if a member, false if not */
protected boolean isBlockOrInlineItem(String nsURI, String lName) { return (isBlockItem(nsURI, lName) || isInlineItem(nsURI, lName)); }
Convenience method for validity checking. Checks if the incoming node is a member of the neutral item list as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
Params:
  • nsURI – namespace URI of incoming node
  • lName – local name (i.e., no prefix) of incoming node
Returns:true if a member, false if not
/** * Convenience method for validity checking. Checks if the * incoming node is a member of the neutral item list * as defined in Sect. 6.2 of the XSL 1.0 &amp; 1.1 Recommendations * @param nsURI namespace URI of incoming node * @param lName local name (i.e., no prefix) of incoming node * @return true if a member, false if not */
protected boolean isNeutralItem(String nsURI, String lName) { return (FO_URI.equals(nsURI) && ("multi-switch".equals(lName) || "multi-properties".equals(lName) || "wrapper".equals(lName) || (!isOutOfLineFODescendant && "float".equals(lName)) || "retrieve-marker".equals(lName) || "retrieve-table-marker".equals(lName))); }
Convenience method for validity checking. Checks if the current node has an ancestor of a given name.
Params:
  • ancestorID – ID of node name to check for (e.g., FO_ROOT)
Returns:number of levels above FO where ancestor exists, -1 if not found
/** * Convenience method for validity checking. Checks if the * current node has an ancestor of a given name. * @param ancestorID ID of node name to check for (e.g., FO_ROOT) * @return number of levels above FO where ancestor exists, * -1 if not found */
protected int findAncestor(int ancestorID) { int found = 1; FONode temp = getParent(); while (temp != null) { if (temp.getNameId() == ancestorID) { return found; } found += 1; temp = temp.getParent(); } return -1; }
Clears the list of child nodes.
/** * Clears the list of child nodes. */
public void clearChildNodes() { this.firstChild = null; }
Returns:the "id" property.
/** @return the "id" property. */
public String getId() { return id; }
Returns:whether this object has an id set
/** @return whether this object has an id set */
public boolean hasId() { return (id != null && id.length() > 0); }
Returns:the "layer" property.
/** @return the "layer" property. */
public String getLayer() { return layer; }
Returns:whether this object has an layer set
/** @return whether this object has an layer set */
public boolean hasLayer() { return (layer != null && layer.length() > 0); }
{@inheritDoc}
/** {@inheritDoc} */
public String getNamespaceURI() { return FOElementMapping.URI; }
{@inheritDoc}
/** {@inheritDoc} */
public String getNormalNamespacePrefix() { return "fo"; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isBidiRangeBlockItem() { String ns = getNamespaceURI(); String ln = getLocalName(); return !isNeutralItem(ns, ln) && isBlockItem(ns, ln); }
Recursively set resolved bidirectional level of FO (and its ancestors) if and only if it is non-negative and if either the current value is reset (-1) or the new value is less than the current value.
Params:
  • bidiLevel – a non-negative bidi embedding level
/** * Recursively set resolved bidirectional level of FO (and its ancestors) if * and only if it is non-negative and if either the current value is reset (-1) * or the new value is less than the current value. * @param bidiLevel a non-negative bidi embedding level */
public void setBidiLevel(int bidiLevel) { assert bidiLevel >= 0; if ((this.bidiLevel < 0) || (bidiLevel < this.bidiLevel)) { this.bidiLevel = bidiLevel; if ((parent != null) && !isBidiPropagationBoundary()) { FObj foParent = (FObj) parent; int parentBidiLevel = foParent.getBidiLevel(); if ((parentBidiLevel < 0) || (bidiLevel < parentBidiLevel)) { foParent.setBidiLevel(bidiLevel); } } } }
Obtain resolved bidirectional level of FO.
Returns:either a non-negative bidi embedding level or -1 in case no bidi levels have been assigned
/** * Obtain resolved bidirectional level of FO. * @return either a non-negative bidi embedding level or -1 * in case no bidi levels have been assigned */
public int getBidiLevel() { return bidiLevel; }
Obtain resolved bidirectional level of FO or nearest FO ancestor that has a resolved level.
Returns:either a non-negative bidi embedding level or -1 in case no bidi levels have been assigned to this FO or any ancestor
/** * Obtain resolved bidirectional level of FO or nearest FO * ancestor that has a resolved level. * @return either a non-negative bidi embedding level or -1 * in case no bidi levels have been assigned to this FO or * any ancestor */
public int getBidiLevelRecursive() { for (FONode fn = this; fn != null; fn = fn.getParent()) { if (fn instanceof FObj) { int level = ((FObj) fn).getBidiLevel(); if (level >= 0) { return level; } } if (isBidiInheritanceBoundary()) { break; } } return -1; } protected boolean isBidiBoundary(boolean propagate) { return false; } private boolean isBidiInheritanceBoundary() { return isBidiBoundary(false); } private boolean isBidiPropagationBoundary() { return isBidiBoundary(true); }
Add a new extension attachment to this FObj. (see org.apache.fop.fo.FONode for details)
Params:
  • attachment – the attachment to add.
/** * Add a new extension attachment to this FObj. * (see org.apache.fop.fo.FONode for details) * * @param attachment the attachment to add. */
void addExtensionAttachment(ExtensionAttachment attachment) { if (attachment == null) { throw new NullPointerException( "Parameter attachment must not be null"); } if (extensionAttachments == null) { extensionAttachments = new java.util.ArrayList<ExtensionAttachment>(); } if (log.isDebugEnabled()) { log.debug("ExtensionAttachment of category " + attachment.getCategory() + " added to " + getName() + ": " + attachment); } extensionAttachments.add(attachment); }
Returns:the extension attachments of this FObj.
/** @return the extension attachments of this FObj. */
public List<ExtensionAttachment> getExtensionAttachments() { if (extensionAttachments == null) { return Collections.EMPTY_LIST; } else { return extensionAttachments; } }
Returns:true if this FObj has extension attachments
/** @return true if this FObj has extension attachments */
public boolean hasExtensionAttachments() { return extensionAttachments != null; }
Adds a foreign attribute to this FObj.
Params:
  • attributeName – the attribute name as a QName instance
  • value – the attribute value
/** * Adds a foreign attribute to this FObj. * @param attributeName the attribute name as a QName instance * @param value the attribute value */
public void addForeignAttribute(QName attributeName, String value) { /* TODO: Handle this over FOP's property mechanism so we can use * inheritance. */ if (attributeName == null) { throw new NullPointerException("Parameter attributeName must not be null"); } if (foreignAttributes == null) { foreignAttributes = new java.util.HashMap<QName, String>(); } foreignAttributes.put(attributeName, value); }
Returns:the map of foreign attributes
/** @return the map of foreign attributes */
public Map getForeignAttributes() { if (foreignAttributes == null) { return Collections.EMPTY_MAP; } else { return foreignAttributes; } }
{@inheritDoc}
/** {@inheritDoc} */
public String toString() { return (super.toString() + "[@id=" + this.id + "]"); }
Basic FONodeIterator implementation
/** Basic {@link FONode.FONodeIterator} implementation */
public static class FObjIterator implements FONodeIterator { private static final int F_NONE_ALLOWED = 0; private static final int F_SET_ALLOWED = 1; private static final int F_REMOVE_ALLOWED = 2; private FONode currentNode; private final FObj parentNode; private int currentIndex; private int flags = F_NONE_ALLOWED; FObjIterator(FObj parent) { this.parentNode = parent; this.currentNode = parent.firstChild; this.currentIndex = 0; this.flags = F_NONE_ALLOWED; }
{@inheritDoc}
/** {@inheritDoc} */
public FObj parent() { return parentNode; }
{@inheritDoc}
/** {@inheritDoc} */
public FONode next() { if (currentNode != null) { if (currentIndex != 0) { if (currentNode.siblings != null && currentNode.siblings[1] != null) { currentNode = currentNode.siblings[1]; } else { throw new NoSuchElementException(); } } currentIndex++; flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED); return currentNode; } else { throw new NoSuchElementException(); } }
{@inheritDoc}
/** {@inheritDoc} */
public FONode previous() { if (currentNode.siblings != null && currentNode.siblings[0] != null) { currentIndex--; currentNode = currentNode.siblings[0]; flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED); return currentNode; } else { throw new NoSuchElementException(); } }
{@inheritDoc}
/** {@inheritDoc} */
public void set(FONode newNode) { if ((flags & F_SET_ALLOWED) == F_SET_ALLOWED) { if (currentNode == parentNode.firstChild) { parentNode.firstChild = newNode; } else { FONode.attachSiblings(currentNode.siblings[0], newNode); } if (currentNode.siblings != null && currentNode.siblings[1] != null) { FONode.attachSiblings(newNode, currentNode.siblings[1]); } if (currentNode == parentNode.lastChild) { parentNode.lastChild = newNode; } } else { throw new IllegalStateException(); } }
{@inheritDoc}
/** {@inheritDoc} */
public void add(FONode newNode) { if (currentIndex == -1) { if (currentNode != null) { FONode.attachSiblings(newNode, currentNode); } parentNode.firstChild = newNode; currentIndex = 0; currentNode = newNode; if (parentNode.lastChild == null) { parentNode.lastChild = newNode; } } else { if (currentNode.siblings != null && currentNode.siblings[1] != null) { FONode.attachSiblings(newNode, currentNode.siblings[1]); } FONode.attachSiblings(currentNode, newNode); if (currentNode == parentNode.lastChild) { parentNode.lastChild = newNode; } } flags &= F_NONE_ALLOWED; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean hasNext() { return (currentNode != null) && ((currentIndex == 0) || (currentNode.siblings != null && currentNode.siblings[1] != null)); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean hasPrevious() { return (currentIndex != 0) || (currentNode.siblings != null && currentNode.siblings[0] != null); }
{@inheritDoc}
/** {@inheritDoc} */
public int nextIndex() { return currentIndex + 1; }
{@inheritDoc}
/** {@inheritDoc} */
public int previousIndex() { return currentIndex - 1; }
{@inheritDoc}
/** {@inheritDoc} */
public void remove() { if ((flags & F_REMOVE_ALLOWED) == F_REMOVE_ALLOWED) { parentNode.removeChild(currentNode); if (currentIndex == 0) { //first node removed currentNode = parentNode.firstChild; } else if (currentNode.siblings != null && currentNode.siblings[0] != null) { currentNode = currentNode.siblings[0]; currentIndex--; } else { currentNode = null; } flags &= F_NONE_ALLOWED; } else { throw new IllegalStateException(); } }
{@inheritDoc}
/** {@inheritDoc} */
public FONode last() { while (currentNode != null && currentNode.siblings != null && currentNode.siblings[1] != null) { currentNode = currentNode.siblings[1]; currentIndex++; } return currentNode; }
{@inheritDoc}
/** {@inheritDoc} */
public FONode first() { currentNode = parentNode.firstChild; currentIndex = 0; return currentNode; } } }