All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.xpath.axes.AxesWalker Maven / Gradle / Ivy

There is a newer version: 4.7.0
Show newest version
/*
 * 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.
 */
package org.htmlunit.xpath.axes;

import java.util.Vector;
import org.htmlunit.xpath.Expression;
import org.htmlunit.xpath.XPathContext;
import org.htmlunit.xpath.XPathVisitor;
import org.htmlunit.xpath.compiler.Compiler;
import org.htmlunit.xpath.res.XPATHErrorResources;
import org.htmlunit.xpath.res.XPATHMessages;
import org.htmlunit.xpath.xml.dtm.DTM;
import org.htmlunit.xpath.xml.dtm.DTMAxisTraverser;
import org.htmlunit.xpath.xml.dtm.DTMIterator;

/** Serves as common interface for axes Walkers, and stores common state variables. */
public class AxesWalker extends PredicatedNodeTest implements Cloneable, PathComponent {

  /**
   * Construct an AxesWalker using a LocPathIterator.
   *
   * @param locPathIterator non-null reference to the parent iterator.
   */
  public AxesWalker(final LocPathIterator locPathIterator, final int axis) {
    super(locPathIterator);
    m_axis = axis;
  }

  public final WalkingIterator wi() {
    return (WalkingIterator) m_lpi;
  }

  /**
   * Initialize an AxesWalker during the parse of the XPath expression.
   *
   * @param compiler The Compiler object that has information about this walker in the op map.
   * @param opPos The op code position of this location step.
   * @param stepType The type of location step.
   * @throws javax.xml.transform.TransformerException if any
   */
  public void init(final Compiler compiler, final int opPos, final int stepType)
      throws javax.xml.transform.TransformerException {

    initPredicateInfo(compiler, opPos);
  }

  /** {@inheritDoc} */
  @Override
  public Object clone() throws CloneNotSupportedException {
    // Do not access the location path iterator during this operation!

    return super.clone();
  }

  /**
   * Do a deep clone of this walker, including next and previous walkers. If the this AxesWalker is
   * on the clone list, don't clone but return the already cloned version.
   *
   * @param cloneOwner non-null reference to the cloned location path iterator to which this clone
   *     will be added.
   * @param cloneList non-null vector of sources in odd elements, and the corresponding clones in
   *     even vectors.
   * @return non-null clone, which may be a new clone, or may be a clone contained on the cloneList.
   */
  AxesWalker cloneDeep(final WalkingIterator cloneOwner, final Vector cloneList)
      throws CloneNotSupportedException {
    AxesWalker clone = findClone(this, cloneList);
    if (null != clone) {
        return clone;
    }
    clone = (AxesWalker) this.clone();
    clone.setLocPathIterator(cloneOwner);
    if (null != cloneList) {
      cloneList.addElement(this);
      cloneList.addElement(clone);
    }

    if (wi().m_lastUsedWalker == this) {
        cloneOwner.m_lastUsedWalker = clone;
    }

    if (null != m_nextWalker) {
        clone.m_nextWalker = m_nextWalker.cloneDeep(cloneOwner, cloneList);
    }

    // If you don't check for the cloneList here, you'll go into an
    // recursive infinate loop.
    if (null != cloneList) {
      if (null != m_prevWalker) {
          clone.m_prevWalker = m_prevWalker.cloneDeep(cloneOwner, cloneList);
      }
    }
    else {
      if (null != m_nextWalker) {
          clone.m_nextWalker.m_prevWalker = clone;
      }
    }
    return clone;
  }

  /**
   * Find a clone that corresponds to the key argument.
   *
   * @param key The original AxesWalker for which there may be a clone.
   * @param cloneList vector of sources in odd elements, and the corresponding clones in even
   *     vectors, may be null.
   * @return A clone that corresponds to the key, or null if key not found.
   */
  static AxesWalker findClone(final AxesWalker key, final Vector cloneList) {
    if (null != cloneList) {
      // First, look for clone on list.
      final int n = cloneList.size();
      for (int i = 0; i < n; i += 2) {
        if (key == cloneList.elementAt(i)) {
            return cloneList.elementAt(i + 1);
        }
      }
    }
    return null;
  }

  /**
   * Detaches the walker from the set which it iterated over, releasing any computational resources
   * and placing the iterator in the INVALID state.
   */
  public void detach() {
    m_currentNode = DTM.NULL;
    m_dtm = null;
    m_traverser = null;
    m_isFresh = true;
    m_root = DTM.NULL;
  }

  // =============== TreeWalker Implementation ===============

  /**
   * The root node of the TreeWalker, as specified in setRoot(int root). Note that this may actually
   * be below the current node.
   *
   * @return The context node of the step.
   */
  public int getRoot() {
    return m_root;
  }

  /** {@inheritDoc} */
  @Override
  public int getAnalysisBits() {
    final int axis = getAxis();
    return WalkerFactory.getAnalysisBitFromAxes(axis);
  }

  /**
   * Set the root node of the TreeWalker. (Not part of the DOM2 TreeWalker interface).
   *
   * @param root The context node of this step.
   */
  public void setRoot(final int root) {
    // %OPT% Get this directly from the lpi.
    final XPathContext xctxt = wi().getXPathContext();
    m_dtm = xctxt.getDTM(root);
    m_traverser = m_dtm.getAxisTraverser(m_axis);
    m_isFresh = true;
    m_foundLast = false;
    m_root = root;
    m_currentNode = root;

    if (DTM.NULL == root) {
      throw new RuntimeException(
          XPATHMessages.createXPATHMessage(
              XPATHErrorResources.ER_SETTING_WALKER_ROOT_TO_NULL, null));
    }

    resetProximityPositions();
  }

  /**
   * Set the next walker in the location step chain.
   *
   * @param walker Reference to AxesWalker derivative, or may be null.
   */
  public void setNextWalker(final AxesWalker walker) {
    m_nextWalker = walker;
  }

  /**
   * Get the next walker in the location step chain.
   *
   * @return Reference to AxesWalker derivative, or null.
   */
  public AxesWalker getNextWalker() {
    return m_nextWalker;
  }

  /**
   * Set or clear the previous walker reference in the location step chain.
   *
   * @param walker Reference to previous walker reference in the location step chain, or null.
   */
  public void setPrevWalker(final AxesWalker walker) {
    m_prevWalker = walker;
  }

  /**
   * Get the next node in document order on the axes.
   *
   * @return the next node in document order on the axes, or null.
   */
  protected int getNextNode() {
    if (m_foundLast) {
        return DTM.NULL;
    }

    if (m_isFresh) {
      m_currentNode = m_traverser.first(m_root);
      m_isFresh = false;
    }
    // I shouldn't have to do this the check for current node, I think.
    // numbering\numbering24.xsl fails if I don't do this. I think
    // it occurs as the walkers are backing up. -sb
    else if (DTM.NULL != m_currentNode) {
      m_currentNode = m_traverser.next(m_root, m_currentNode);
    }

    if (DTM.NULL == m_currentNode) {
        this.m_foundLast = true;
    }

    return m_currentNode;
  }

  /**
   * Moves the TreeWalker to the next visible node in document order relative to the
   * current node, and returns the new node. If the current node has no next node, or if the search
   * for nextNode attempts to step upward from the TreeWalker's root node, returns 
   * null , and retains the current node.
   *
   * @return The new node, or null if the current node has no next node in the
   *     TreeWalker's logical view.
   */
  public int nextNode() {
    int nextNode = DTM.NULL;
    AxesWalker walker = wi().getLastUsedWalker();

    while (true) {
      if (null == walker) {
          break;
      }

      nextNode = walker.getNextNode();

      if (DTM.NULL == nextNode) {

        walker = walker.m_prevWalker;
      }
      else {
        if (walker.acceptNode(nextNode) != DTMIterator.FILTER_ACCEPT) {
          continue;
        }

        if (null == walker.m_nextWalker) {
          wi().setLastUsedWalker(walker);

          // return walker.returnNextNode(nextNode);
          break;
        }

        final AxesWalker prev = walker;
        walker = walker.m_nextWalker;
        walker.setRoot(nextNode);
        walker.m_prevWalker = prev;
        continue;
      }
    }

    return nextNode;
  }

  // ============= End TreeWalker Implementation =============

  /** {@inheritDoc} */
  @Override
  public int getLastPos(final XPathContext xctxt) {

    int pos = getProximityPosition();

    final AxesWalker walker;

    try {
      walker = (AxesWalker) clone();
    }
    catch (final CloneNotSupportedException cnse) {
      return -1;
    }

    walker.setPredicateCount(m_predicateIndex);
    walker.setNextWalker(null);
    walker.setPrevWalker(null);

    final WalkingIterator lpi = wi();
    final AxesWalker savedWalker = lpi.getLastUsedWalker();

    try {
      lpi.setLastUsedWalker(walker);

      while (DTM.NULL != walker.nextNode()) {
        pos++;
      }

      // TODO: Should probably save this in the iterator.
    }
    finally {
      lpi.setLastUsedWalker(savedWalker);
    }

    // System.out.println("pos: "+pos);
    return pos;
  }

  // ============= State Data =============

  /**
   * The DTM for the root. This can not be used, or must be changed, for the filter walker, or any
   * walker that can have nodes from multiple documents. Never, ever, access this value without
   * going through getDTM(int node).
   */
  private DTM m_dtm;

  /**
   * Get the DTM for this walker.
   *
   * @return Non-null reference to a DTM.
   */
  public DTM getDTM(final int node) {
    //
    return wi().getXPathContext().getDTM(node);
  }

  /**
   * Returns the axis being iterated, if it is known.
   *
   * @return Axis.CHILD, etc., or -1 if the axis is not known or is of multiple types.
   */
  public int getAxis() {
    return m_axis;
  }

  /** {@inheritDoc} */
  @Override
  public void callVisitors(final XPathVisitor visitor) {
    if (visitor.visitStep()) {
      callPredicateVisitors(visitor);
      if (null != m_nextWalker) {
        m_nextWalker.callVisitors(visitor);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean deepEquals(final Expression expr) {
    if (!super.deepEquals(expr)) {
        return false;
    }

    final AxesWalker walker = (AxesWalker) expr;
    return this.m_axis == walker.m_axis;
  }

  /** The root node of the TreeWalker, as specified when it was created. */
  transient int m_root = DTM.NULL;

  /** The node at which the TreeWalker is currently positioned. */
  private transient int m_currentNode = DTM.NULL;

  /** True if an itteration has not begun. */
  transient boolean m_isFresh;

  /**
   * The next walker in the location step chain.
   *
   * @serial
   */
  protected AxesWalker m_nextWalker;

  /**
   * The previous walker in the location step chain, or null.
   *
   * @serial
   */
  AxesWalker m_prevWalker;

  /** The traversal axis from where the nodes will be filtered. */
  protected final int m_axis;

  /** The DTM inner traversal class, that corresponds to the super axis. */
  protected DTMAxisTraverser m_traverser;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy