| /* |
| * 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$ */ |
| |
| package org.apache.fop.fo; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| 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.ChangeBar; |
| import org.apache.fop.fo.flow.Marker; |
| import org.apache.fop.fo.pagination.PageSequence; |
| 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. |
| */ |
| public abstract class FObj extends FONode implements Constants { |
| |
| /** the list of property makers */ |
| private static final PropertyMaker[] PROPERTY_LIST_TABLE |
| = FOPropertyMapping.getGenericMappings(); |
| |
| /** pointer to the descendant subtree */ |
| protected FONode firstChild; |
| |
| /** pointer to the end of the descendant subtree */ |
| protected FONode lastChild; |
| |
| /** The list of extension attachments, null if none */ |
| private List<ExtensionAttachment> extensionAttachments; |
| |
| /** 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. |
| */ |
| private boolean isOutOfLineFODescendant; |
| |
| /** 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 |
| |
| private boolean forceKeepTogether; |
| |
| /** |
| * 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} */ |
| 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. |
| * @param propId the property ID |
| * @return the requested Property Maker |
| */ |
| public static PropertyMaker getPropertyMakerFor(int propId) { |
| return PROPERTY_LIST_TABLE[propId]; |
| } |
| |
| /** {@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} |
| */ |
| 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. |
| * @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 FOPException FOP Exception |
| */ |
| public void startOfNode() throws FOPException { |
| if (id != null) { |
| checkId(id); |
| } |
| |
| PageSequence pageSequence = getRoot().getLastPageSequence(); |
| if (pageSequence != null && pageSequence.hasChangeBars()) { |
| startOfNodeChangeBarList = pageSequence.getClonedChangeBarList(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @throws FOPException FOP Exception |
| */ |
| public void endOfNode() throws FOPException { |
| |
| List<ChangeBar> endOfNodeChangeBarList = null; |
| |
| PageSequence pageSequence = getRoot().getLastPageSequence(); |
| if (pageSequence != null) { |
| endOfNodeChangeBarList = pageSequence.getClonedChangeBarList(); |
| } |
| |
| if (startOfNodeChangeBarList != null && endOfNodeChangeBarList != null) { |
| |
| nodeChangeBarList = new LinkedList<ChangeBar>(endOfNodeChangeBarList); |
| nodeChangeBarList.retainAll(startOfNodeChangeBarList); |
| |
| if (nodeChangeBarList.isEmpty()) { |
| nodeChangeBarList = null; |
| } |
| |
| startOfNodeChangeBarList = null; |
| } |
| |
| super.endOfNode(); |
| } |
| |
| /** |
| * 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. |
| * @return true if Out of Line FO or Out Of Line descendant, false otherwise |
| */ |
| boolean getIsOutOfLineFODescendant() { |
| return isOutOfLineFODescendant; |
| } |
| |
| /** {@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 |
| * @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} */ |
| 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 |
| * @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. |
| * @return true if generates reference areas |
| * TODO see if needed |
| */ |
| public boolean generatesReferenceAreas() { |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| public FONodeIterator getChildNodes() { |
| if (hasChildren()) { |
| return new FObjIterator(this); |
| } |
| return null; |
| } |
| |
| /** |
| * 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) |
| * @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()). |
| * @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. |
| * @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); |
| } |
| } |
| |
| /** |
| * @return true if there are any Markers attached to this object |
| */ |
| public boolean hasMarkers() { |
| return markers != null && !markers.isEmpty(); |
| } |
| |
| /** |
| * @return the collection of Markers attached to this object |
| */ |
| public Map<String, Marker> getMarkers() { |
| return markers; |
| } |
| |
| /** {@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} */ |
| 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 |
| * |
| * @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 |
| * |
| * @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) |
| || "change-bar-begin".equals(lName) |
| || "change-bar-end".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 |
| * @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 |
| * @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. |
| * @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. |
| */ |
| public void clearChildNodes() { |
| this.firstChild = null; |
| } |
| |
| /** @return the "id" property. */ |
| public String getId() { |
| return id; |
| } |
| |
| /** @return whether this object has an id set */ |
| public boolean hasId() { |
| return (id != null && id.length() > 0); |
| } |
| |
| /** @return the "layer" property. */ |
| public String getLayer() { |
| return layer; |
| } |
| |
| /** @return whether this object has an layer set */ |
| public boolean hasLayer() { |
| return (layer != null && layer.length() > 0); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getNamespaceURI() { |
| return FOElementMapping.URI; |
| } |
| |
| /** {@inheritDoc} */ |
| public String getNormalNamespacePrefix() { |
| return "fo"; |
| } |
| |
| /** {@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. |
| * @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. |
| * @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. |
| * @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) |
| * |
| * @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); |
| } |
| |
| /** @return the extension attachments of this FObj. */ |
| public List<ExtensionAttachment> getExtensionAttachments() { |
| if (extensionAttachments == null) { |
| return Collections.EMPTY_LIST; |
| } else { |
| return extensionAttachments; |
| } |
| } |
| |
| /** @return true if this FObj has extension attachments */ |
| public boolean hasExtensionAttachments() { |
| return extensionAttachments != null; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** @return the map of foreign attributes */ |
| public Map getForeignAttributes() { |
| if (foreignAttributes == null) { |
| return Collections.EMPTY_MAP; |
| } else { |
| return foreignAttributes; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() { |
| return (super.toString() + "[@id=" + this.id + "]"); |
| } |
| |
| public boolean isForceKeepTogether() { |
| return forceKeepTogether; |
| } |
| |
| public void setForceKeepTogether(boolean b) { |
| forceKeepTogether = b; |
| } |
| |
| /** 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} */ |
| public FObj parent() { |
| return parentNode; |
| } |
| |
| /** {@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} */ |
| 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} */ |
| 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} */ |
| 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} */ |
| public boolean hasNext() { |
| return (currentNode != null) |
| && ((currentIndex == 0) |
| || (currentNode.siblings != null |
| && currentNode.siblings[1] != null)); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean hasPrevious() { |
| return (currentIndex != 0) |
| || (currentNode.siblings != null |
| && currentNode.siblings[0] != null); |
| } |
| |
| /** {@inheritDoc} */ |
| public int nextIndex() { |
| return currentIndex + 1; |
| } |
| |
| /** {@inheritDoc} */ |
| public int previousIndex() { |
| return currentIndex - 1; |
| } |
| |
| /** {@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} */ |
| public FONode last() { |
| while (currentNode != null |
| && currentNode.siblings != null |
| && currentNode.siblings[1] != null) { |
| currentNode = currentNode.siblings[1]; |
| currentIndex++; |
| } |
| return currentNode; |
| } |
| |
| /** {@inheritDoc} */ |
| public FONode first() { |
| currentNode = parentNode.firstChild; |
| currentIndex = 0; |
| return currentNode; |
| } |
| } |
| } |