Package dk.brics.xact.analysis.xmlgraph

Source Code of dk.brics.xact.analysis.xmlgraph.XMLGraphAnalysis

package dk.brics.xact.analysis.xmlgraph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dk.brics.automaton.Automaton;
import dk.brics.automaton.State;
import dk.brics.automaton.Transition;
import dk.brics.xact.XMLXPathException;
import dk.brics.xact.analysis.XMLAnalysisException;
import dk.brics.xact.analysis.dataflow.VariableAnalysisInterface;
import dk.brics.xact.analysis.flowgraph.SchemaType;
import dk.brics.xact.analysis.flowgraph.Statement;
import dk.brics.xact.analysis.flowgraph.statements.CastStm;
import dk.brics.xact.analysis.flowgraph.statements.CheckStm;
import dk.brics.xact.analysis.flowgraph.statements.ConcatStm;
import dk.brics.xact.analysis.flowgraph.statements.ConstStm;
import dk.brics.xact.analysis.flowgraph.statements.CopyStm;
import dk.brics.xact.analysis.flowgraph.statements.EmptyStm;
import dk.brics.xact.analysis.flowgraph.statements.GapifyStm;
import dk.brics.xact.analysis.flowgraph.statements.GetStm;
import dk.brics.xact.analysis.flowgraph.statements.InsertStm;
import dk.brics.xact.analysis.flowgraph.statements.NodeStm;
import dk.brics.xact.analysis.flowgraph.statements.PlugStm;
import dk.brics.xact.analysis.flowgraph.statements.RemoveStm;
import dk.brics.xact.analysis.flowgraph.statements.SetStm;
import dk.brics.xact.analysis.flowgraph.statements.UnknownStm;
import dk.brics.xact.analysis.flowgraph.statements.ValidateStm;
import dk.brics.xact.analysis.flowgraph.statements.GetStm.Kind;
import dk.brics.xmlgraph.AttributeNode;
import dk.brics.xmlgraph.ChoiceNode;
import dk.brics.xmlgraph.ConcreteNode;
import dk.brics.xmlgraph.ElementNode;
import dk.brics.xmlgraph.InterleaveNode;
import dk.brics.xmlgraph.MultiContentNode;
import dk.brics.xmlgraph.Node;
import dk.brics.xmlgraph.NodeProcessor;
import dk.brics.xmlgraph.OneOrMoreNode;
import dk.brics.xmlgraph.ReachableNodesProcessor;
import dk.brics.xmlgraph.SequenceNode;
import dk.brics.xmlgraph.SingleContentNode;
import dk.brics.xmlgraph.TextNode;
import dk.brics.xmlgraph.XMLGraph;
import dk.brics.xmlgraph.XMLGraphFragment;
import dk.brics.xpath.evaluator.EvaluationContext;
import dk.brics.xpath.evaluator.StatusMap;
import dk.brics.xpath.evaluator.XPathEvaluator;

/**
* XML graph lattice and transfer functions for dataflow analysis.
*/
public class XMLGraphAnalysis implements VariableAnalysisInterface<XMLGraph> {
  /** Every XML graph in the lattice is a copy of this with different edges */
  private XMLGraph global_xg;

  private ElementNode dummy_root;

  private ChoiceNode dummy_root_content;

  private StatementNodes stm_nodes;

  private XPathEvaluator evaluator; // TODO: use jaxen-based XPath evaluator on XML graphs?

  private EvaluationContext evaluation_context;

  private Set<Statement> empty_xpath;

  private Set<Statement> check_fails;

  private Set<Integer> concreteNodes;
 
  /**
   * Constructs a new XML graph dataflow analysis.
   */
  public XMLGraphAnalysis(XMLGraph global_xg,
      ElementNode dummy_root,
      ChoiceNode dummy_root_content,
      Map<String,String> namespaces,
      StatementNodes stm_nodes,
      Set<Statement> empty_xpath,
      Set<Statement> check_fails) {
    this.global_xg = global_xg;
    this.dummy_root = dummy_root;
    this.dummy_root_content = dummy_root_content;
    this.stm_nodes = stm_nodes;
    this.empty_xpath = empty_xpath;
    this.check_fails = check_fails;
    evaluator = new XPathEvaluator();
    evaluation_context = new EvaluationContext();
    evaluation_context.setDefaultNamespace("");
    evaluation_context.addNamespaces(namespaces);
    evaluator.setEvaluationContext(evaluation_context);
   
    concreteNodes = new LinkedHashSet<Integer>();
    for (Node node : global_xg.getNodes()) {
      if (node instanceof ConcreteNode) {
        concreteNodes.add(node.getIndex());
      }
    }
  }
 
  /**
   * See {@link #restoreEdges(ChoiceNode, XMLGraph)}.
   */
  private void restoreEdges(int index, XMLGraph g) {
    restoreEdges((ChoiceNode)g.getNode(index), g);
  }
  /**
   * Restores the edges that were added by XMLGraphBuilder, but may have been
   * removed by {@link XMLGraph#sharpen()}.
   * <p/>
   * The choice node will only be replaced if necessary. The caller is NOT allowed
   * to modify the set returned by <tt>node.getContents()</tt> after this call.
   * @param node a choice node
   * @param g graph in which to replace the edges
   */
  private void restoreEdges(ChoiceNode node, XMLGraph g) {
    int index = node.getIndex();
    ChoiceNode gx = (ChoiceNode)global_xg.getNode(index);
    if (node.getContents() == gx.getContents())
      return;
    if (node.getContents().containsAll(gx.getContents()))
      return;
    if (node.getContents().isEmpty()) {
      node.setContent(gx.getContents(), g);
    } else {
      LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(node.getContents());
      cs.addAll(gx.getContents());
      node.setContent(cs, g);
    }
  }
 
  public XMLGraph newBottomElement() {
    return global_xg.clone();
  }

  public boolean merge(XMLGraph source, XMLGraph dest) {
    return dest.merge(source);
  }

  public void assign(XMLGraph g, Statement s) {
    //throw new RuntimeException();
//    XMLGraphDebug.dumpXMLGraph(g, s);
  }

  public XMLGraph transferCast(final CastStm s, final XMLGraph base) {
    final XMLGraph g = base.clone();
    if (g.isUnknown()) {
      return g;
    }
    final Emptiness emptiness;
    switch (s.getKind()) {
    case ASATTRIBUTE:
        emptiness = new Emptiness(g, null, EPresence.BOTTOM, EPresence.BOTTOM, EPresence.BOTTOM);
        break;
    case ASELEMENT:
        emptiness = new Emptiness(g, EPresence.BOTTOM, null, EPresence.BOTTOM, EPresence.BOTTOM);
        break;
    case ASTEXT:
        emptiness = new Emptiness(g, EPresence.BOTTOM, EPresence.BOTTOM, null, EPresence.BOTTOM);
        break;
    case ASCOMMENT:
    case ASPI:
    case ASNSDECL:
    default:
        return g;
    }
    final FirstRootAnalysis first = new FirstRootAnalysis(g, emptiness, s.getKind() == CastStm.Kind.ASTEXT);
   
    g.processReachableNodes(new NodeProcessor<Object>() {
        @Override
        public Object process(ChoiceNode n) {
            if (!first.get(n.getIndex()).definitely())
                return this;
            LinkedList<Integer> remove = new LinkedList<Integer>();
            for (int child : n.getContents()) {
                if (first.get(child).definitelyNot()) {
                    remove.add(child);
                }
            }
            if (!remove.isEmpty()) {
                LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
                cs.removeAll(remove);
                n.setContent(cs, g);
            }
            return this;
        }
    });
    LinkedList<Integer> remove = new LinkedList<Integer>();
    for (int root : g.getRoots()) {
        if (first.get(root).definitelyNot()) {
            remove.add(root);
        }
    }
    g.getRoots().removeAll(remove);
   
    return g;
  }

  public void transferCheck(CheckStm s, XMLGraph base) {
    StatusMap sm = evaluateXPathOrRoot(s.getXPath(), base);
    switch (s.getKind()) {
    case GETNUMBER:
      checkXPathEmpty(s, base, sm);
      checkGetNumberOrStringFails(s, base, sm, true);
      break;
    case GETSTRING:
      checkXPathEmpty(s, base, sm);
      checkGetNumberOrStringFails(s, base, sm, false);
      break;
    case HAS:
    case ISATTRIBUTE:
    case ISELEMENT:
    case ISTEXT:
      checkNodeTestFails(s, base, sm);
      break;
    case TODOCUMENT:
      checkOneRootFails(s, base, sm);
      break;
    }
  }
 
  public XMLGraph transferConcat(ConcatStm s, XMLGraph xmlsource) {
    XMLGraph g = xmlsource.clone();
    if (g.isUnknown()) {
      return g;
    }
    ChoiceNode cn = stm_nodes.getConcatChoiceNode(s,g);
    Set<Integer> cs = new LinkedHashSet<Integer>();
    //cs.addAll(cn.getContents());
    stm_nodes.getConcatTextNode(s, g).replaceText(s.getStringSource(), g);
    if (!s.getStringSource().isEmpty()) {
      cs.add(stm_nodes.getConcatTextNode(s, g).getIndex());
    }
    cs.add(stm_nodes.getConcatSequenceNode(s, g).getIndex());
    cs.addAll(g.getRoots());
    cn.setContent(cs, g);
    g.getRoots().clear();
    g.getRoots().add(stm_nodes.getConcatOneOrMoreNode(s, g).getIndex());
    //stm_nodes.getConcatTextNode(s,g).replaceText(s.getStringSource(), g);
    g.sharpen();
    return g;
  }

  public XMLGraph transferConst(ConstStm s) {
    final XMLGraph g = global_xg.clone();
    g.useFragment(stm_nodes.getTemplateConstantXMLFragment(s.getConst()));
    // open gaps
    g.processReachableNodes(new NodeProcessor<Object>() {
      @Override
      public Object process(ChoiceNode n) {
        if (n.isGap() && !n.isOpen()) {
          n.setContentAndStatus(true, false, n.getContents(), g);
        }
        return this;
      }
    });
    g.sharpen();
    return g;
  }
 
  private static final class AutomatonPresence {
    public Automaton auto;
    public EPresence presence;
    public static final AutomatonPresence NoStringUnknown = new AutomatonPresence(Automaton.makeEmpty(), EPresence.UNKNOWN);
    public static final AutomatonPresence NoStringEmpty = new AutomatonPresence(Automaton.makeEmpty(), EPresence.EMPTY);
    public AutomatonPresence(Automaton auto, EPresence presence) {
      this.auto = auto;
      this.presence = presence;
    }
  }

  /**
   * Returns the possible names of the first root element in any unfolding of the given XML graph.
   * Attributes and text nodes are ignored.
   */
  private Automaton getFirstElementNames(XMLGraph g) {
    return getFirstElementNames(g, g.getRoots());
  }

  /**
   * Returns the possible names of the first element in some unfolding starting at any of the given
   * nodes.
   */
  private Automaton getFirstElementNames(final XMLGraph g, Set<Integer> startNodes) {
    return applyAutomatonPresence(g, startNodes, new CachedNodeProcessor<AutomatonPresence>() {
      @Override
      public AutomatonPresence cycle() {
        return AutomatonPresence.NoStringUnknown;
      }
      @Override
      public AutomatonPresence process(AttributeNode n) {
        return AutomatonPresence.NoStringEmpty;
      }
      @Override
      public AutomatonPresence process(ElementNode n) {
        return new AutomatonPresence(n.getName(), EPresence.NONEMPTY);
      }
      @Override
      public AutomatonPresence process(TextNode n) {
        return AutomatonPresence.NoStringEmpty;
      }
      @Override
      public AutomatonPresence process(ChoiceNode n) {
        return choiceOrInterleave(n);
      }
      @Override
      public AutomatonPresence process(InterleaveNode n) {
        return choiceOrInterleave(n);
      }
      private AutomatonPresence choiceOrInterleave(MultiContentNode n) {
        List<Automaton> auto = new ArrayList<Automaton>();
        EPresence presence = EPresence.BOTTOM;
        for (int child : n.getContents()) {
          AutomatonPresence p = g.getNode(child).process(this);
          auto.add(p.auto);
          presence = presence.leastUpperBound(p.presence);
        }
        return new AutomatonPresence(Automaton.union(auto), presence);
      }
      @Override
      public AutomatonPresence process(SequenceNode n) {
        List<Automaton> auto = new ArrayList<Automaton>();
        EPresence presence = EPresence.BOTTOM;
        for (int child : n.getContents()) {
          AutomatonPresence p = g.getNode(child).process(this);
          auto.add(p.auto);
          presence = presence.concat(p.presence);
          if (presence.definitelyNonEmpty())
            break;
        }
        return new AutomatonPresence(Automaton.union(auto), presence);
      }
      @Override
      public AutomatonPresence process(OneOrMoreNode n) {
        return g.getNode(n.getContent()).process(this);
      }
    }).auto;
  }
 
//  private Automaton getAttributeNames(XMLGraph g) {
//    return getAttributeNames(g, g.getRoots());
//  }
 
//  /**
//   * Returns the possible names of the root attributes in some unfolding starting at any of the given
//   * nodes.
//   */
//  private Automaton getAttributeNames(final XMLGraph g, Set<Integer> startNodes) {
//    CachedNodeProcessor<Automaton> p = new CachedNodeProcessor<Automaton>() {
//      @Override
//      public Automaton cycle() {
//        return Automaton.makeEmpty();
//      }
//      @Override
//      public Automaton process(AttributeNode n) {
//        return n.getName();
//      }
//      @Override
//      public Automaton process(ElementNode n) {
//        return Automaton.makeEmpty();
//      }
//      @Override
//      public Automaton process(TextNode n) {
//        return Automaton.makeEmpty();
//      }
//      @Override
//      public Automaton process(SingleContentNode n) {
//        return g.getNode(n.getContent()).process(this);
//      }
//      @Override
//      public Automaton process(MultiContentNode n) {
//        List<Automaton> autos = new ArrayList<Automaton>();
//        for (int child : n.getContents()) {
//          autos.add(g.getNode(child).process(this));
//        }
//        return Automaton.union(autos);
//      }
//      @Override
//      public Automaton process(NoContentNode n) {
//        return Automaton.makeEmpty();
//      }
//    };
//    List<Automaton> autos = new ArrayList<Automaton>();
//    for (int root : startNodes) {
//      autos.add(g.getNode(root).process(p));
//    }
//    return Automaton.union(autos);
//  }
 
  /**
   * Applies the given processor to all root nodes and returns the union of the
   * resulting automatons.
   */
  private AutomatonPresence applyAutomatonPresence(XMLGraph g, Set<Integer> startNodes, NodeProcessor<AutomatonPresence> processor) {
    List<Automaton> autos = new ArrayList<Automaton>();
    EPresence presence = EPresence.BOTTOM;
    for (int root : startNodes) {
      AutomatonPresence p = g.getNode(root).process(processor);
      autos.add(p.auto);
      presence = presence.leastUpperBound(p.presence);
    }
    return new AutomatonPresence(Automaton.union(autos), presence);
  }
 
  /**
   * Applies the given processor to all root nodes and discards the results.
   */
  private void apply(XMLGraph g, NodeProcessor<?> processor) {
    for (int root : g.getRoots()) {
      g.getNode(root).process(processor);
    }
  }
 
  private boolean unknown(XMLGraph g) {
    return g != null && g.isUnknown();
  }
 
  public XMLGraph transferCopy(final CopyStm s, final XMLGraph base, XMLGraph firstattr, XMLGraph firstchild, XMLGraph nextnode) {
    XMLGraph g = base.clone();
    if (base.isUnknown() || unknown(firstattr) || unknown(firstchild) || unknown(nextnode)) {
      g.setUnknown();
      return g;
    }
    restoreEdges(stm_nodes.getCopyLeftChoice(s, g), g);
    switch (s.getKind()) {
    case ELEMENT:
      stm_nodes.getCopyElementNode(s, g).setName(getFirstElementNames(base), g);
      restoreEdges(stm_nodes.getCopyElementNode(s, g).getContent(), g);
      if (firstattr != null) {
        g.merge(firstattr);
        stm_nodes.getCopyFirstAttribute(s, g).setContent(firstattr.getRoots(), g);
      }
      if (firstchild != null) {
        g.merge(firstchild);
        stm_nodes.getCopyFirstChild(s, g).setContent(firstchild.getRoots(), g);
      }
      if (nextnode != null) {
        g.merge(nextnode);
        stm_nodes.getCopyNextNode(s, g).setContent(nextnode.getRoots(), g);
      }
      g.getRoots().clear();
      g.getRoots().add(stm_nodes.getCopyTopNode(s, g).getIndex());
      break;
    case COMMENT:
    case PROCESSINGINSTRUCTION:
      g = nextnode.clone();
      break;
    case ATTRIBUTE:
    case ATTRNODE:
    case ATTRIBUTEGAP:
    case TEMPLATEGAP:
    case TEMPNODE:
    case TEXT:
      ChoiceNode left = stm_nodes.getCopyLeftChoice(s, g);
      final LinkedHashSet<Integer> leftContent = new LinkedHashSet<Integer>();
      if (nextnode != null) {
        g.merge(nextnode);
        g.getRoots().clear();
        g.getRoots().add(stm_nodes.getCopyTopNode(s, g).getIndex());
        stm_nodes.getCopyNextNode(s, g).setContent(nextnode.getRoots(), g);
      }
      apply(base, new CachedNodeProcessor<EPresence>() {
        @Override
        public EPresence cycle() {
          return EPresence.UNKNOWN;
        }
        @Override
        public EPresence process(AttributeNode n) {
          leftContent.add(n.getIndex());
          //addChild(left, n.getIndex(), g);
          //left.getContents().add(n.getIndex());
          return EPresence.NONEMPTY;
        }
        @Override
        public EPresence process(ElementNode n) {
          return EPresence.BOTTOM;
        }
        @Override
        public EPresence process(OneOrMoreNode n) {
          return base.getNode(n.getContent()).process(this);
        }
        @Override
        public EPresence process(TextNode n) {
          if (s.getKind() == CopyStm.Kind.TEXT) {
            if (n.getText().isEmpty())
              return EPresence.BOTTOM;
            leftContent.add(n.getIndex());
            //addChild(left, child, g)
            //left.getContents().add(n.getIndex());
            if (n.getText().isEmptyString())
              return EPresence.EMPTY;
            else if (n.getText().run(""))
              return EPresence.UNKNOWN;
            else
              return EPresence.NONEMPTY;
          }
          return EPresence.BOTTOM;
        }
        @Override
        public EPresence process(ChoiceNode n) {
          EPresence p = EPresence.BOTTOM;
          if (n.isGap() && n.isOpen()) {
            if (base.getOpenAttributeGaps().contains(n.getName()) || base.getOpenTemplateGaps().contains(n.getName())) {
              //left.getContents().add(n.getIndex());
              leftContent.add(n.getIndex());
              p = EPresence.UNKNOWN;
            }
          }
          for (int child : n.getContents()) {
            p = p.leastUpperBound(base.getNode(child).process(this));
          }
          return p;
        }
        @Override
        public EPresence process(SequenceNode n) {
          EPresence p = EPresence.EMPTY;
          for (int child : n.getContents()) {
            p = p.concat(base.getNode(child).process(this));
            if (p.definitelyNonEmpty())
              break;
          }
          return p;
        }
        @Override
        public EPresence process(InterleaveNode n) {
          EPresence p = EPresence.BOTTOM;
          for (int child : n.getContents()) {
            p = p.leastUpperBound(base.getNode(child).process(this));
          }
          return p;
        }
      });
      left.setContent(leftContent, g);
      //left.getContents().addAll(base.getRoots());
      break;
    default:
      throw new RuntimeException("unexpected copy type " + s.getKind());
    }
    g.sharpen();
    return g;
  }
 
  public XMLGraph transferEmpty(EmptyStm s) {
    XMLGraph g = global_xg.clone();
    return g;
  }

  public XMLGraph transferGapify(final GapifyStm s, final XMLGraph base) {
    final XMLGraph g = base.clone();
    if (g.isUnknown()) {
      return g;
    }
    final ChoiceNode gap_node = stm_nodes.getGapifyChoiceNode(s,g);
    StatusMap smt;
    //try {
      //smt = evaluator.evaluate(base, s.getXPath(), dummy_root, dummy_root_content);
    smt = evaluateXPathOrRoot(s.getXPath(), base);
    //} catch (XPathException e) {
    //  throw new XMLXPathException(e);
    //}
    final StatusMap sm = smt;
    checkXPathEmpty(s, base, sm);
    g.processRoots(new ReachableNodesProcessor(g) { 
      @Override
      public Object process(ChoiceNode n) { // all concrete nodes have a choice node parent
        Collection<Integer> cs = new LinkedHashSet<Integer>();
        boolean modified = false;
        boolean add_gap = false;
        for (int i : n.getContents()) {
          boolean keep = true;
          boolean hit = false;
          Node c = g.getNode(i);
          if (c instanceof ConcreteNode) {
            switch (sm.get(i)) {
            case ALL:
            case DEFINITE:
              hit = true;
              keep = false;
              break;
            case SOME:
            case DONTKNOW:
              hit = true;
              break;
            case NEVER:
            case NONE:
              break;
            }
          }
          if (keep)
            cs.add(i);
          else
            modified = true;
          if (hit) {
            add_gap = true;
            boolean tgap = false;
            boolean agap = false;
            if (c instanceof ElementNode)
              tgap = true;
            else if (c instanceof AttributeNode)
              agap = true;
            else if (c instanceof TextNode) { // TODO: determine whether this is TextNode a tgap or an agap (or both)
              tgap = true;
              agap = true;
            }
            String gapname = s.getGapName();
            if (tgap)
              g.addOpenTemplateGap(gapname);
            if (agap)
              g.addOpenAttributeGap(gapname);
            if (s.getSchema() != null) {
              if (g.getGapTypeMap().get(gapname) != null &&
                  !g.getGapTypeMap().get(gapname).equals(s.getSchema().getType()))
                throw new XMLAnalysisException("Different types of gap '" + gapname + "'", s.getOrigin());
              g.getGapTypeMap().put(gapname, s.getSchema().getType());
            }
          }
        }
        if (add_gap) {
          cs.add(gap_node.getIndex());
          modified = true;
        }
        if (modified) {
          n.setContent(cs, g);
          gap_node.setContentAndStatus(true, gap_node.isRemoved(), gap_node.getContents(), g);
        }
        return null;
      }
    });
    g.sharpen();
    return g;
  }
 
  private Set<ElementNode> getFirstRootElements(final XMLGraph g) {
    final Set<ElementNode> rootelements = new LinkedHashSet<ElementNode>();
    for (int root : g.getRoots()) {
      g.getNode(root).process(new CachedNodeProcessor<EPresence>() {
        @Override
        public EPresence cycle() {
          return EPresence.UNKNOWN;
        }
        @Override
        public EPresence process(AttributeNode n) {
          return EPresence.EMPTY;
        }
        @Override
        public EPresence process(ElementNode n) {
          rootelements.add(n);
          return EPresence.NONEMPTY;
        }
        @Override
        public EPresence process(ChoiceNode n) {
          EPresence result = EPresence.BOTTOM;
          for (int child : n.getContents()) {
            result = result.leastUpperBound(g.getNode(child).process(this));
          }
          return result;
        }
        @Override
        public EPresence process(OneOrMoreNode n) {
          return g.getNode(n.getContent()).process(this);
        }
        @Override
        public EPresence process(SequenceNode n) {
          EPresence result = EPresence.EMPTY;
          for (int child : n.getContents()) {
            result = result.concat(g.getNode(child).process(this));
            if (result.definitelyNonEmpty())
              break;
          }
          return result;
        }
        @Override
        public EPresence process(InterleaveNode n) {
          EPresence result = EPresence.BOTTOM;
          for (int child : n.getContents()) {
            result = result.leastUpperBound(g.getNode(child).process(this));
          }
          return result;
        }
        @Override
        public EPresence process(TextNode n) {
          return EPresence.EMPTY;
        }
      });
    }
    return rootelements;
  }

  public XMLGraph transferGet(final GetStm s, final XMLGraph base) {
    final XMLGraph g = base.clone();
    if (g.isUnknown()) {
      return g;
    }
    ChoiceNode cn = stm_nodes.getGetChoiceNode(s, g);
    final LinkedHashSet<Integer> c = new LinkedHashSet<Integer>();
    cn.setContent(c, g);
    switch (s.getKind()) {
    case GET:
    case GETELEMENT: // TODO: to improve analysis precision, only select the *first* element for getElement
    case GETELEMENTS: {
      StatusMap sm;
//      try {
//        sm = evaluator.evaluate(base, s.getXPath(), dummy_root, dummy_root_content);
//      } catch (XPathException e) {
//        throw new XMLXPathException(e);
//      }
      sm = evaluateXPathOrRoot(s.getXPath(), base);
      checkXPathEmpty(s, base, sm);
      for (int i : sm.getNodes()) {
        if (g.getNode(i) instanceof ConcreteNode) {
          StatusMap.Status status = sm.get(i);
          if (status!=StatusMap.Status.NONE && status!=StatusMap.Status.NEVER)
            c.add(i);
        }
      }
      c.addAll(cn.getContents());
      g.getRoots().clear();
      g.addRoot(cn);
      // TODO: could improve precision by considering simple XPath *predicates*
      break;
    }
    case GETFIRSTATTR:
    case GETFIRSTATTRIBUTE:
    case GETFIRSTCHILD:
      g.getRoots().clear();
      g.addRoot(cn);
      Set<ElementNode> rootElements = getFirstRootElements(base);
      for (ElementNode node : rootElements) {
        g.getNode(node.getContent()).process(new CachedNodeProcessor<EPresence>() {
          @Override
          public EPresence cycle() {
            return EPresence.UNKNOWN;
          }
          @Override
          public EPresence process(AttributeNode n) {
            c.add(n.getIndex());
            return EPresence.NONEMPTY;
          }
          @Override
          public EPresence process(ElementNode n) {
            if (s.getKind() == Kind.GETFIRSTATTR || s.getKind() == Kind.GETFIRSTATTRIBUTE) {
              return EPresence.EMPTY;
            } else {
              c.add(n.getIndex());
              return EPresence.NONEMPTY;
            }
          }
          @Override
          public EPresence process(ChoiceNode n) {
            if (n.isGap() && n.isOpen() && s.getKind() == Kind.GETFIRSTATTR && g.getOpenAttributeGaps().contains(n.getName())) {
              c.add(n.getIndex());
              return EPresence.UNKNOWN;
            } else {
              EPresence result = EPresence.BOTTOM;
              for (int child : n.getContents()) {
                result = result.leastUpperBound(g.getNode(child).process(this));
              }
              return result;
            }
          }
          @Override
          public EPresence process(SequenceNode n) {
            EPresence result = EPresence.EMPTY;
            for (int child : n.getContents()) {
              result = result.concat(g.getNode(child).process(this));
              if (result.definitelyNonEmpty())
                break;
            }
            return result;
          }
          @Override
          public EPresence process(OneOrMoreNode n) {
            return g.getNode(n.getContent()).process(this);
          }
          @Override
          public EPresence process(TextNode n) {
            return EPresence.EMPTY;
          }
          @Override
          public EPresence process(InterleaveNode n) {
            EPresence result = EPresence.BOTTOM;
            for (int child : n.getContents()) {
              result = result.leastUpperBound(g.getNode(child).process(this));
            }
            return result;
          }
        });
      }
      break;
     
    case GETFIRSTELEMENT: {
      final Emptiness emptiness = new Emptiness(g, EPresence.EMPTY, EPresence.NONEMPTY, EPresence.EMPTY, EPresence.EMPTY);
      final FirstRootAnalysis first = new FirstRootAnalysis(g, emptiness, false);
      final RootAnalysis roots = new RootAnalysis(g);
          final int emptyIndex = stm_nodes.getGetEmptySequence(s, g).getIndex();
      // we model this as "replace every root node that is not the first root element with the empty sequence"
      g.processReachableNodes(new NodeProcessor<Object>() {
          @Override
          public Object process(ChoiceNode n) {
              EBooleanLattice r = roots.get(n.getIndex());
              if (r.definitelyNot()) {
                  return this;
              }
              LinkedList<Integer> removed = new LinkedList<Integer>();
              boolean addempty = false;
              for (int child : n.getContents()) {
                  if (roots.get(child).maybe()) {
                            if (r.definitely()) {
                                if (first.get(child).definitelyNot() || emptiness.get(child).definitelyEmpty()) {
                                    removed.add(child);
                                }
                            }
                            if (first.get(child).maybeNot() && emptiness.get(child).maybeNonEmpty()) {
                                addempty = true;
                            }
                  }
              }
              boolean removegap = false;
                    if (n.isGap() && n.isOpen() && emptiness.get(n.getIndex()).definitelyEmpty() && r.definitely()) {
                        removegap = true;
                    }
              if (!removed.isEmpty() || addempty || removegap) {
                        LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
                        cs.removeAll(removed);
                        cs.add(emptyIndex);
                        if (removegap) {
                            n.setContentAndStatus(false, true, cs, g);
                        } else {
                            n.setContent(cs, g);
                        }
              }
              return this;
          }
      });
      LinkedList<Integer> removed = new LinkedList<Integer>();
      for (int child : g.getRoots()) {
          switch (emptiness.get(child)) {
          case EMPTY:
                case BOTTOM:
              // if no ELEMENT can occur as root here, then remove the root edge
              removed.add(child);
              break;
            default: ;
          }
      }
      g.getRoots().removeAll(removed);
      // note: no need to add an empty root, because getFirstElement() throws
      // an exception if none of the roots are elements
     
      break;
    }
     
    case GETNEXTATTR:
    case GETNEXTATTRIBUTE:
    case GETNEXTSIBLING: {
      Emptiness emptiness = new Emptiness(g,
              null,                                    // allow attributes
              s.getKind() == Kind.GETNEXTSIBLING ? null : EPresence.BOTTOM,      // allow elements
              s.getKind() == Kind.GETNEXTSIBLING ? null : EPresence.BOTTOM,      // allow text
              s.getKind() != Kind.GETNEXTATTRIBUTE ? null : EPresence.BOTTOM);   // allow gaps
      final FirstRootAnalysis first = new FirstRootAnalysis(g, emptiness, s.getKind() == Kind.GETNEXTSIBLING);
          final int emptyIndex = stm_nodes.getGetEmptySequence(s, g).getIndex();
         
      // We model the "get next sibling" operations as "replace first root with empty sequece".
            // If a ChoiceNode has an edge to a ConcreteNode that might be the first root,
            // then the ChoiceNode gets an additional edge to the empty sequence.
      // If that ConcreteNode can only occur as the first root, then the edge to it is removed.
     
      g.processReachableNodes(new NodeProcessor<Object>() {
          @Override
          public Object process(ChoiceNode n) {
              if (first.get(n.getIndex()).definitelyNot())
                  return this;
              LinkedList<Integer> removed = new LinkedList<Integer>();
              boolean addempty = false;
              for (int child : n.getContents()) {
                  if (!(g.getNode(child) instanceof ConcreteNode))
                      continue;
                  switch (first.get(child)) {
                  case YES:
//                      removed.add(child); // cannot remove due to invisible comments and PIs :(
                      addempty = true;
                      break;
                  case MAYBE:
                      addempty = true;
                      break;
                    default: ;
                  }
              }
              if (removed.isEmpty() || addempty) {
                  LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
                  cs.removeAll(removed);
                  cs.add(emptyIndex);
                  n.setContent(cs, g);
              }
              return removed;
          }
      });
            LinkedList<Integer> removed = new LinkedList<Integer>();
      boolean addempty = false;
      for (int root : g.getRoots()) {
          if (!(g.getNode(root) instanceof ConcreteNode))
                    continue;
          switch (first.get(root)) {
                case YES:
//                  removed.add(root); // cannot remove due to invisible comments and PIs :(
                    addempty = true;
                    break;
                case MAYBE:
                    addempty = true;
                    break;
                default: ;
                }
      }
      g.getRoots().removeAll(removed);
      if (addempty) {
          g.getRoots().add(emptyIndex);
      }
     
      break;
    }
   
    default:
      throw new RuntimeException("Unexpected GET kind: " + s.getKind());
    }//switch
    g.sharpen();
    return g;
  }
 
  /**
   * Evaluates the given XPath expression if it is non-null, and otherwise it evaluates the "/" XPath
   * expression identifying which nodes are always, sometimes, and never roots.
   * @param xpath an XPath expression or <tt>null</tt>
   * @param g an XML graph
   * @return the status map returned by {@link XPathEvaluator#evaluate(XMLGraph, String, ElementNode, ChoiceNode)}
   */
  private StatusMap evaluateXPathOrRoot(String xpath, XMLGraph g) {
    if (xpath == null) {
      xpath = "/node()|/@*";
    }
    try {
      return evaluator.evaluate(g, xpath, dummy_root, dummy_root_content);
    } catch (Exception ex) {
      throw new XMLXPathException(ex);
    }
   
  }
 
  private ChoiceNode makePrivate(ChoiceNode ch, XMLGraph g) {
    ch.setContent(new LinkedHashSet<Integer>(ch.getContents()), g);
    return (ChoiceNode)g.getNode(ch.getIndex());
  }
 
  public XMLGraph transferInsert(final InsertStm s, XMLGraph base, final XMLGraph xmlsrc) {
    final XMLGraph g = base.clone();
    if (g.isUnknown() || xmlsrc.isUnknown()) {
      g.setUnknown();
      return g;
    }
    final SequenceNode seq = stm_nodes.getInsertSequenceNode(s, g);
    final ChoiceNode left;
    final ChoiceNode right;
    final TextNode text = stm_nodes.getInsertTextNode(s, g);
   
    switch (s.getKind()) {
    case APPEND:
    case APPENDCONTENT:
    case INSERTAFTER:
    case SETATTRIBUTE:
      left = makePrivate(stm_nodes.getInsertLeftSide(s, g), g);
      right = makePrivate(stm_nodes.getInsertRightSide(s, g), g);
      break;
    case PREPEND:
    case PREPENDCONTENT:
    case INSERTBEFORE:
      // SWAP left and right if prepending
      left =  makePrivate(stm_nodes.getInsertRightSide(s, g), g);
      right = makePrivate(stm_nodes.getInsertLeftSide(s, g), g);
      break;
    default:
      throw new RuntimeException("unknown insert kind");
    }
    text.replaceText(s.getStringSource(), g);
    right.getContents().addAll(xmlsrc.getRoots());
    right.getContents().add(text.getIndex());

    switch (s.getKind()) {
    case APPEND:
    case PREPEND:
      g.merge(xmlsrc);
      left.getContents().addAll(base.getRoots());
      g.getRoots().clear();
      g.getRoots().add(seq.getIndex());
      return g;

    case SETATTRIBUTE:
    case APPENDCONTENT: // this is both appendContent(XML) and appendContent(XPath,XML)
    case PREPENDCONTENT:{
      final StatusMap stm = evaluateXPathOrRoot(s.getXPath(), g);
      boolean empty = checkXPathEmpty(s, g, stm);
      if (empty)
        return g;
      g.merge(xmlsrc);
      g.getRoots().retainAll(base.getRoots());
      // TODO can we append as root here???
      g.processReachableNodes(new NodeProcessor<Object>() {
        @Override
        public Object process(ElementNode n) {
          ChoiceNode ch = (ChoiceNode)g.getNode(n.getContent());
          switch (stm.get(n.getIndex())) {
          case DEFINITE:
          case ALL:
            left.getContents().addAll(ch.getContents());
            ch = makePrivate(ch, g);
            if (g.getGapTypeMap().isEmpty()) {
                ch.getContents().clear();
            }
            ch.getContents().add(seq.getIndex());
            break;
          case SOME:
          case DONTKNOW:
            ch = makePrivate(ch, g);
            left.getContents().addAll(ch.getContents());
            ch.getContents().add(seq.getIndex());
            break;
          case NONE:
          case NEVER:
            break;
          }
          return null;
        }
      });
      g.sharpen();
      return g;
    }
    case INSERTAFTER:
    case INSERTBEFORE: {
      final StatusMap stm = evaluateXPathOrRoot(s.getXPath(), g);
      boolean empty = checkXPathEmpty(s, g, stm);
      if (empty)
        return g;
      g.merge(xmlsrc);
      g.getRoots().retainAll(base.getRoots());
      g.processReachableNodes(new NodeProcessor<Object>() {
        @Override
        public Object process(ChoiceNode ch) {
          LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(ch.getContents());
          for (int child : ch.getContents()) {
            Node node = g.getNode(child);
            if (node instanceof ConcreteNode) {
              switch (stm.get(child)) {
              case ALL:
              case DEFINITE:
                  if (g.getGapTypeMap().isEmpty()) {
                      cs.remove(child);
                  }
                left.getContents().add(child);
                cs.add(seq.getIndex());
                break;
              case SOME:
              case DONTKNOW:
                left.getContents().add(child);
                cs.add(seq.getIndex());
                break;
              case NONE:
              case NEVER:
                break;
              }
            }
          }
          ch.setContent(cs, g);
          return null;
        }
      });
      g.sharpen();
      return g;
    }
    default:
      throw new RuntimeException("unknown insert kind");
    }
  }

  private void mergeContents(ChoiceNode node, Collection<Integer> toadd, XMLGraph g) {
      LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(node.getContents());
      cs.addAll(toadd);
      node.setContent(cs, g);
  }
 
    private void mergeOpen(ChoiceNode node, XMLGraph g) {
        node.setContentAndStatus(true, node.isRemoved(), node.getContents(), g);
    }
   
  public XMLGraph transferNode(NodeStm s, XMLGraph firstattr, XMLGraph firstchild, XMLGraph nextnode) {
    XMLGraph g = global_xg.clone();
    if (unknown(firstattr) || unknown(firstchild) || unknown(nextnode)) {
      g.setUnknown();
      return g;
    }
    g.getRoots().clear();
    g.getRoots().add(stm_nodes.getStmNode(s, g).getIndex());
    if (nextnode != null) {
      g.merge(nextnode);
      mergeContents(stm_nodes.getStmNextNode(s, g), nextnode.getRoots(), g);
    }
    if (firstattr != null) {
      g.merge(firstattr);
            mergeContents(stm_nodes.getStmFirstAttribute(s, g), firstattr.getRoots(), g);
    }
    if (firstchild != null) {
      g.merge(firstchild);
            mergeContents(stm_nodes.getStmFirstChild(s, g), firstchild.getRoots(), g);
    }
    g.getRoots().clear();
    g.getRoots().add(stm_nodes.getStmNode(s, g).getIndex());
    switch (s.getKind()) {
    case ATTRIBUTEGAP:
        mergeOpen(stm_nodes.getStmGap(s, g), g);
      g.addOpenAttributeGap(s.getGap());
      break;
    case TEMPLATEGAP:
            mergeOpen(stm_nodes.getStmGap(s, g), g);
      g.addOpenTemplateGap(s.getGap());
      break;
    default: ;
    }
    g.sharpen();
    return g;
  }

  public XMLGraph transferPlug(final PlugStm s, XMLGraph base, final XMLGraph xmlsrc) {
    /*final XMLGraph g = global_xg.clone();
    g.getRoots().addAll(base.getRoots());
    g.merge(base);*/
    final XMLGraph g = base.clone();
    if (base.isUnknown() || unknown(xmlsrc)) {
      g.setUnknown();
      return g;
    }
    switch (s.getKind()) {
    case PLUG:
    case PLUGMULTI:
    case PLUGWRAP:
      final String gapname = s.getGapName();
      // plug roots into the relevant gaps and merge the XML graphs
      final boolean skip_str = s.getStringSource().isEmpty();
      if (g.getOpenAttributeGaps().contains(gapname) || g.getOpenTemplateGaps().contains(gapname)) {
        g.processRoots(new ReachableNodesProcessor(g) { 
          @Override
          public Object process(ChoiceNode n) {
            if (n.isGap() && n.isOpen() && n.getName().equals(gapname)) {
              Collection<Integer> c = new LinkedHashSet<Integer>();
              c.addAll(n.getContents());
              c.addAll(xmlsrc.getRoots());
              if (!skip_str)
                c.add(stm_nodes.getPlugTextNode(s,g).getIndex());
              n.setContentAndStatus(false, n.isRemoved(), c, g);
            }
            return null;
          }
        });
      }
      if (!skip_str) {
        TextNode tn = stm_nodes.getPlugTextNode(s,g);
        ArrayList<Automaton> ss = new ArrayList<Automaton>();
        ss.add(s.getStringSource());
        ss.add(tn.getText());
        tn.replaceText(Automaton.union(ss), g);
      }
      g.merge(xmlsrc);
      if (s.getKind() == PlugStm.Kind.PLUGWRAP) {
        g.getRoots().clear();
        g.getRoots().add(stm_nodes.getPlugWrapTopNode(s, g).getIndex());
        restoreEdges(stm_nodes.getPlugWrapTopNode(s, g), g);
        stm_nodes.getPlugWrapContentNode(s, g).setContent(base.getRoots(), g);
      } else {
        g.getRoots().retainAll(base.getRoots());
      }
      // mark the gap as closed
      if (g.getOpenTemplateGaps().contains(gapname))
        g.addClosedTemplateGap(gapname);
      g.removeOpenTemplateGap(gapname);
      if (g.getOpenAttributeGaps().contains(gapname))
        g.addClosedAttributeGap(gapname);
      g.removeOpenAttributeGap(gapname);
      g.getGapTypeMap().remove(gapname);
      // open again if the inserted value contains the gap
      if (xmlsrc.getOpenTemplateGaps().contains(gapname)) {
        g.addOpenTemplateGap(gapname);
        if (!xmlsrc.getClosedTemplateGaps().contains(gapname))
          g.removeClosedTemplateGap(gapname);
        String gaptype = xmlsrc.getGapTypeMap().get(gapname);
        if (gaptype != null)
          g.getGapTypeMap().put(gapname, gaptype);
     
      if (xmlsrc.getOpenAttributeGaps().contains(gapname)) {
        g.addOpenAttributeGap(gapname);
        if (!xmlsrc.getClosedAttributeGaps().contains(gapname))
          g.removeClosedAttributeGap(gapname);
        String gaptype = xmlsrc.getGapTypeMap().get(gapname);
        if (gaptype != null)
          g.getGapTypeMap().put(gapname, gaptype);
     
//      XMLGraphDebug.dumpXMLGraph(g, s);
      break;
    case CLOSE:
      g.processRoots(new ReachableNodesProcessor(g) { 
        @Override
        public Object process(ChoiceNode n) {
          if (n.isGap() && n.isOpen()) {
            Collection<Integer> c = new LinkedHashSet<Integer>();
            c.addAll(n.getContents());
            c.add(stm_nodes.getPlugSequenceNode(s,g).getIndex());
            n.setContentAndStatus(false, n.isRemoved(), c, g);
          }
          return null;
        }
      });
      g.getClosedTemplateGaps().addAll(g.getOpenTemplateGaps());
      g.getClosedAttributeGaps().addAll(g.getOpenAttributeGaps());
      g.getOpenTemplateGaps().clear();
      g.getOpenAttributeGaps().clear();
      g.getGapTypeMap().clear();
      break;
    }
    g.sharpen();
    return g;
  }
 
  public XMLGraph transferRemove(final RemoveStm s, final XMLGraph base) {
    final XMLGraph g = base.clone();
    if (g.isUnknown()) {
      return g;
    }
    StatusMap smt;
    smt = evaluateXPathOrRoot(s.getXPath(), base); //evaluator.evaluate(base, s.getXPath(), dummy_root, dummy_root_content);
    final StatusMap sm = smt;
    checkXPathEmpty(s, base, sm);
    g.processRoots(new ReachableNodesProcessor(g) { 
      @Override
      public Object process(ChoiceNode n) { // all concrete nodes have a choice node parent
        Collection<Integer> cs = new LinkedHashSet<Integer>();
        boolean modified = false;
        boolean add_emptyseq = false;
        for (int i : n.getContents()) {
          boolean keep = true;
          if (base.getNode(i) instanceof ConcreteNode) {
            switch (sm.get(i)) {
            case ALL:
            case DEFINITE:
              add_emptyseq = true;
              if (g.getGapTypeMap().isEmpty()) {
                  keep = false;
              }
              modified = true;
              break;
            case SOME:
            case DONTKNOW:
              add_emptyseq = true;
              modified = true;
              break;
            case NEVER:
            case NONE:
              break;
            }
          }
          if (keep)
            cs.add(i);
        }
        if (add_emptyseq)
          cs.add(stm_nodes.getRemoveSequenceNode(s,g).getIndex());
        if (modified)
          n.setContent(cs, g);
        return null;
      }
    });
    g.sharpen();
    return g;
  }

  public XMLGraph transferSet(SetStm s, XMLGraph base, final XMLGraph xmlsrc) {
    final XMLGraph g = base.clone();
    if (g.isUnknown() || xmlsrc.isUnknown()) {
      g.setUnknown();
      return g;
    }

    final StatusMap stm = evaluateXPathOrRoot(s.getXPath(), g);

    boolean empty = checkXPathEmpty(s, g, stm);
    if (empty)
      return g;

    g.merge(xmlsrc);
    g.getRoots().retainAll(base.getRoots());

    final TextNode text = stm_nodes.getSetTextNode(s, g);
    text.replaceText(s.getStringSource(), g);

    // TODO avoid recursive replacement for nodes that will not actually be replaced
    // because its ancestor got replaced instead.
    switch (s.getKind()) {
    case SET:
      // set(XPath, Object)
      g.processReachableNodes(new NodeProcessor<Object>() {
        @Override
        public Object process(ChoiceNode n) {
          LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
          for (int child : n.getContents()) {
            Node node = g.getNode(child);
            if (node instanceof ConcreteNode) {
              switch (stm.get(child)) {
              case ALL:
              case DEFINITE:
                  if (g.getGapTypeMap().isEmpty()) {
                      cs.remove(child);
                  }
                cs.addAll(xmlsrc.getRoots());
                cs.add(text.getIndex());
                break;
              case SOME:
              case DONTKNOW:
                cs.addAll(xmlsrc.getRoots());
                cs.add(text.getIndex());
                break;
              case NONE:
              case NEVER:
                break;
              }
            }
          }
          n.setContent(cs, g);
          return null;
        }
      });
      break;

    case SETCONTENT:
      g.processReachableNodes(new NodeProcessor<Object>() {
        @Override
        public Object process(ElementNode n) {
          ChoiceNode ch = (ChoiceNode)g.getNode(n.getContent());
          LinkedHashSet<Integer> c = new LinkedHashSet<Integer>(ch.getContents());
          switch (stm.get(n.getIndex())) {
          case ALL:
          case DEFINITE:
              if (g.getGapTypeMap().isEmpty()) {
                  c.clear();
              }
            c.addAll(xmlsrc.getRoots());
            c.add(text.getIndex());
            break;

          case SOME:
          case DONTKNOW:
            c.addAll(xmlsrc.getRoots());
            c.add(text.getIndex());
            break;

          case NONE:
          case NEVER:
            break;
          }
          ch.setContent(c, g);
          return null;
        }
      });
      break;

    default:
      throw new RuntimeException("unknown set kind");
    }

    g.sharpen();

    return g;
  }

  public XMLGraph transferUnknown(UnknownStm s) {
    XMLGraph g = global_xg.clone();
    g.setUnknown(); // TODO: use an ordinary 'any' schema instead?
    return g;
  }

  public XMLGraph transferValidate(final ValidateStm s, XMLGraph base) {
    final SchemaType schema = s.getSchema();
    final XMLGraph g = global_xg.clone();
    g.useFragment(new XMLGraphFragment(schema.getTypeNode(), schema.getTemplateGaps(), schema.getAttributeGaps(), schema.getGapTypes()));
    g.sharpen();
    return g;
  }
 
  /**
   * Returns the XML node corresponding to the given schema type.
   */
//  private SequenceNode getNode(String type, Origin origin) {
//    SequenceNode n = schema_types.get(type);
//    if (n == null) { // TODO: source .rng defines are ncnames, this is just a hack
//      String localname;
//      int i = type.lastIndexOf('}');
//      if (i==-1)
//        localname = type;
//      else
//        localname = type.substring(i+1);
//      n = schema_types.get(localname);
//    }
//    if (n == null)
//      throw new XMLAnalysisException("No schema definition found for " + type, origin);
//    return n;
//  }

  /**
   * Adds (/removes) 's' to 'empty_xpath' if no (/some) concrete node can be selected.
   */
  private boolean checkXPathEmpty(Statement s, XMLGraph xg, StatusMap sm) {
    boolean empty;
    if (xg.isUnknown()) {
      empty = false;
    } else {
      empty = true;
      for (Node n : xg.getNodes()) {
        if (n instanceof ConcreteNode) { // TODO: checkXPathEmpty and other checks ignore comment/PI/nsdecl nodes (nonexisting in XML graphs)
          if (!sm.get(n.getIndex()).equals(StatusMap.Status.NONE)) {
            empty = false;
            break;
          }
        }
      }
    }
    if (empty)
      empty_xpath.add(s);
    else
      empty_xpath.remove(s);
    return empty;
  }

  /**
   * Adds 's' to 'check_fails' if the 'CheckStm' node test always fails.
   * (Otherwise, removes 's'.)
   */
  private void checkNodeTestFails(CheckStm s, XMLGraph xg, StatusMap sm) {
    if (sm == null)
      return;
    boolean empty;
    if (xg.isUnknown()) {
      empty = false;
    } else {
      empty = true;
      loop:
      for (Node n : xg.getNodes()) {
        boolean relevant = false;
        switch (s.getKind()) {
        case HAS:
          relevant = n instanceof ConcreteNode;
          break;
        case ISATTRIBUTE:
          relevant = n instanceof AttributeNode;
          break;
        case ISELEMENT:
          relevant = n instanceof ElementNode;
          break;
        case ISTEXT:
          relevant = n instanceof TextNode;
          break;
        default:
          throw new RuntimeException(s.getKind() + " not expected");
        }
        if (relevant)
          switch (sm.get(n.getIndex())) {
          case ALL:
          case DEFINITE:
          case SOME:
          case DONTKNOW:
            empty = false;
            break loop;
          default: ;
          }
      }
    }
    if (empty)
      check_fails.add(s);
    else
      check_fails.remove(s);
  }

  /**
   * Adds 's' to 'check_fails' if the 'CheckStm' getNumber/getString gives empty result.
   * (Otherwise, removes 's'.)
   */
  private void checkGetNumberOrStringFails(CheckStm s, XMLGraph xg, StatusMap sm, boolean number) {
    if (sm == null)
      return;
    boolean fails;
    if (xg.isUnknown()) {
      fails = false;
    } else {
      fails = true;
      Set<TextNode> reachable = getReachableTextNodes(xg, sm);
      search:  for (TextNode n : reachable) {
        for (State st : n.getText().getStates()) {
          for (Transition tr : st.getTransitions()) {
            if (number) { // look for digits
              if (tr.getMin() <= '9' && tr.getMax() >= '0') {
                fails = false;
                break search;
              }
            } else { // look for non-whitespace chars
              // TODO: check whether tr contains non-whitespace chars
              fails = false;
              break search;
            }
          }
        }
      }
    }
    if (fails)
      check_fails.add(s);
    else
      check_fails.remove(s);
  }

  /**
   * Finds the set of text nodes that are reachable from nodes that may be selected.
   */
  private Set<TextNode> getReachableTextNodes(final XMLGraph xg, final StatusMap sm) {
    final Set<TextNode> textnodes = new LinkedHashSet<TextNode>();
    xg.processRoots(new CachedNodeProcessor<Object>() {
      int depth = 0;
      @Override
      public Object cycle() {
        return this;
      }
      @Override
      public Object process(TextNode n) {
        if (depth > 0)
          textnodes.add(n);
        return null;
      }
      @Override
      public Object process(SingleContentNode n) {
        boolean accepted = maybeAccepted(n);
        if (accepted)
          depth++;
        xg.getNode(n.getContent()).process(this);
        if (accepted)
          depth--;
        return null;
      }
      @Override
      public Object process(MultiContentNode n) {
        boolean accepted = maybeAccepted(n);
        if (accepted)
          depth++;
        for (int child : n.getContents()) {
          xg.getNode(child).process(this);
        }
        if (accepted)
          depth--;
        return null;
      }
      private boolean maybeAccepted(Node node) {
        switch (sm.get(node.getIndex())) {
        case ALL:
        case DEFINITE:
        case DONTKNOW:
        case SOME:
          return true;
        default:
          return false;
        }
      }
    });
    /*xg.processReachableNodes(new NodeProcessor<Object>() {
      @Override
      public Object process(SingleContentNode n) {
        // skip nodes that cannot be selected
        switch (sm.get(n.getIndex())) {
        case NONE:
        case NEVER:
          return null;
        }
        if (xg.getNode(n.getContent()) instanceof TextNode) {
          textnodes.add((TextNode)xg.getNode(n.getContent()));
        }
        return null;
      }
      @Override
      public Object process(MultiContentNode n) {
        switch (sm.get(n.getIndex())) {
        case NONE:
        case NEVER:
          return null;
        }
        for (int i : n.getContents()) {
          if (xg.getNode(i) instanceof TextNode)
            textnodes.add((TextNode)xg.getNode(i));
        }
        return null;
      }
    });*/
    return textnodes;
  }

  /**
   * Adds 's' to 'check_fails' if not exactly one root element.
   * (Otherwise, removes 's'.)
   */
  private void checkOneRootFails(CheckStm s, final XMLGraph xg, StatusMap sm) {
    // TODO use fixpoint search to increase precision here
    final boolean[] malformed = new boolean[1]; // many roots?
    final Set<Node> open = new HashSet<Node>();
    NodeProcessor<EPresence> b = new NodeProcessor<EPresence>() {
      // TODO this takes worst-case exponential time unless results are cached or a fixpoint search is used
      @Override
      public EPresence process(SequenceNode n) {
        if (!open.add(n)) {
          malformed[0] = true; // cycles require a fixpoint search
          return EPresence.UNKNOWN;
        }
        EPresence result = EPresence.EMPTY;
        for (int index : n.getContents()) {
          Node child = xg.getNode(index);
          EPresence p = child.process(this);
          if (result.maybeNonEmpty() && p.maybeNonEmpty())
            malformed[0] = true; // many roots
          result = result.concat(p);
        }
        open.remove(n);
        return result;
      }
      @Override
      public EPresence process(ChoiceNode n) {
        if (!open.add(n))
          return EPresence.UNKNOWN;
        EPresence result = EPresence.BOTTOM;
        for (int index : n.getContents()) {
          Node child = xg.getNode(index);
          EPresence p = child.process(this);
          result = result.leastUpperBound(p);
        }
        open.remove(n);
        return result;
      }
      @Override
      public EPresence process(OneOrMoreNode n) {
        if (!open.add(n))
          return EPresence.UNKNOWN;
        EPresence p = xg.getNode(n.getContent()).process(this);
        open.remove(n);
        if (p.maybeNonEmpty())
          malformed[0] = true; // many roots
        return p;
      }
      @Override
      public EPresence process(TextNode n) {
        if (n.getText().isEmpty()) {
          return EPresence.BOTTOM;
        } else {
          malformed[0] = true; // text at root
          return EPresence.NONEMPTY;
        }
      }
      @Override
      public EPresence process(ElementNode n) {
        return EPresence.NONEMPTY;
      }
      @Override
      public EPresence process(InterleaveNode n) {
        return EPresence.UNKNOWN;
      }
      @Override
      public EPresence process(AttributeNode n) {
        malformed[0] = true; // attribute at root
        return EPresence.UNKNOWN;
      }
    };
    for (int index : xg.getRoots()) {
      if (xg.getNode(index).process(b).maybeEmpty()) {
        malformed[0] = true;
      }
    }
    // TODO when a text node or attribute is root, a different error message than "non-unique"
    // root should be generated
    if (malformed[0])
      check_fails.add(s);
    else
      check_fails.remove(s);
  }

}
TOP

Related Classes of dk.brics.xact.analysis.xmlgraph.XMLGraphAnalysis

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.