gt:         final long start = System.currentTimeMillis();
context: /*
   *  eXist Open Source Native XML Database
   *  Copyright (C) 2001-2015 The eXist Project
   *  http://exist-db.org
   *
   *  This program is free software; you can redistribute it and/or
   *  modify it under the terms of the GNU Lesser General Public License
   *  as published by the Free Software Foundation; either version 2
   *  of the License, or (at your option) any later version.
   *
   *  This program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU Lesser General Public License for more details.
   *
   *  You should have received a copy of the GNU Lesser General Public
   *  License along with this library; if not, write to the Free Software
   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   */
  package org.exist.xquery.functions.fn;
  
  import org.exist.EXistException;
  import org.exist.dom.persistent.DocumentSet;
  import org.exist.dom.persistent.ExtArrayNodeSet;
  import org.exist.dom.persistent.NodeProxy;
  import org.exist.dom.persistent.NodeSet;
  import org.exist.dom.QName;
  import org.exist.storage.DBBroker;
  import org.exist.storage.ElementValue;
  import org.exist.storage.NativeValueIndex;
  import org.exist.util.PatternFactory;
  import org.exist.xquery.pragmas.Optimize;
  import org.exist.xquery.*;
  import org.exist.xquery.util.Error;
  import org.exist.xquery.value.BooleanValue;
  import org.exist.xquery.value.FunctionParameterSequenceType;
  import org.exist.xquery.value.FunctionReturnSequenceType;
  import org.exist.xquery.value.Item;
  import org.exist.xquery.value.Sequence;
  import org.exist.xquery.value.SequenceType;
  import org.exist.xquery.value.StringValue;
  import org.exist.xquery.value.Type;
  
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  import java.util.regex.PatternSyntaxException;
  
  import static org.exist.xquery.regex.RegexUtil.*;
  
  /**
   * Implements the fn:matches() function.
   * 
   * Based on the java.util.regex package for regular expression support.
   * 
   * @author Wolfgang Meier (wolfgang@exist-db.org)
   */
  public class FunMatches extends Function implements Optimizable, IndexUseReporter {
  
  	protected static final String FUNCTION_DESCRIPTION_1_PARAM =
  		"The function returns true if $input matches the regular expression " +
  		"supplied as $pattern, if present; otherwise, it returns false.\n\n";
  	protected static final String FUNCTION_DESCRIPTION_2_PARAM =
  		"The function returns true if $input matches the regular expression " +
  		"supplied as $pattern as influenced by the value of $flags, if present; " +
  		"otherwise, it returns false.\n\n" +
  		"The effect of calling this version of the function with the $flags argument set to a zero-length string is the same as using the other two argument version. " +
  		"Flags are defined in 7.6.1.1 Flags.\n\n";
  
  	protected static final String FUNCTION_DESCRIPTION_COMMON =
  		"If $input is the empty sequence, it is interpreted as the zero-length string.\n\n" +
  		"Unless the metacharacters ^ and $ are used as anchors, the string is considered " +
  		"to match the pattern if any substring matches the pattern. But if anchors are used, " +
  		"the anchors must match the start/end of the string (in string mode), or the " +
  		"start/end of a line (in multiline mode).\n\n" +
  		"Note:\n\n" +
  		"This is different from the behavior of patterns in [XML Schema Part 2: Datatypes " +
  		"Second Edition], where regular expressions are implicitly anchored.\n\n" +
  		"Please note that - in contrast - with the " +
          "specification - this method allows zero or more items for the string argument.\n\n" +
  		"An error is raised [err:FORX0002] if the value of $pattern is invalid " +
  		"according to the rules described in section 7.6.1 Regular Expression Syntax.\n\n";
  	protected static final String FUNCTION_DESCRIPTION_2_PARAM_2 =
  		"An error is raised [err:FORX0001] if the value of $flags is invalid " +
  		"according to the rules described in section 7.6.1 Regular Expression Syntax.";
  	
  	protected static final String FUNCTION_DESCRIPTION_REGEX =
  		"If $input is the empty sequence, it is interpreted as the zero-length string.\n\n" +
  		"Note:\n\n" +
  		"The text:matches-regex() variants of the fn:matches() functions are identical except that they avoid the translation of the specified regular expression from XPath2 to Java syntax. " +
  		"That is, the regular expression is evaluated as is, and must be valid according to Java regular expression syntax, rather than the more restrictive XPath2 syntax.";
  
  	protected static final FunctionParameterSequenceType INPUT_ARG = new FunctionParameterSequenceType("input", Type.STRING, Cardinality.ZERO_OR_ONE, "The input string");
  	protected static final FunctionParameterSequenceType PATTERN_ARG = new FunctionParameterSequenceType("pattern", Type.STRING, Cardinality.EXACTLY_ONE, "The pattern");
  	protected static final FunctionParameterSequenceType FLAGS_ARG = new FunctionParameterSequenceType("flags", Type.STRING, Cardinality.EXACTLY_ONE, "The flags");
  
  	public final static FunctionSignature signatures[] = {
  		new FunctionSignature(
  			new QName("matches", Function.BUILTIN_FUNCTION_NS),
  			FUNCTION_DESCRIPTION_1_PARAM + FUNCTION_DESCRIPTION_COMMON,
  			new SequenceType[] { INPUT_ARG, PATTERN_ARG },
  			new FunctionReturnSequenceType(Type.BOOLEAN, Cardinality.EXACTLY_ONE, "true if the pattern is a match, false otherwise")
  		),
  		new FunctionSignature(
  			new QName("matches", Function.BUILTIN_FUNCTION_NS),
  			FUNCTION_DESCRIPTION_2_PARAM + FUNCTION_DESCRIPTION_COMMON +
              FUNCTION_DESCRIPTION_2_PARAM_2,
  			new SequenceType[] { INPUT_ARG, PATTERN_ARG, FLAGS_ARG },
  			new FunctionReturnSequenceType(Type.BOOLEAN, Cardinality.EXACTLY_ONE, "true if the pattern is a match, false otherwise")
  		)
  	};
  
  	protected Matcher matcher = null;
  	protected Pattern pat = null;
  	
  	protected boolean hasUsedIndex = false;
  
      private LocationStep contextStep = null;
      private QName contextQName = null;
      private int axis = Constants.UNKNOWN_AXIS;
      private NodeSet preselectResult = null;
      private GeneralComparison.IndexFlags idxflags = new GeneralComparison.IndexFlags();
  
      /**
  	 * @param context
  	 */
  	public FunMatches(XQueryContext context, FunctionSignature signature) {
  		super(context, signature);
  	}
  	
  	@Override
  	public void setArguments(List<Expression> arguments) throws XPathException {
          steps.clear();
          final Expression path = arguments.get(0);
          steps.add(path);
          
          if(arguments.size() >= 2) {
              Expression arg = arguments.get(1);
              arg = new DynamicCardinalityCheck(context, Cardinality.EXACTLY_ONE, arg,
                      new Error(Error.FUNC_PARAM_CARDINALITY, "2", mySignature));
              if (!Type.subTypeOf(arg.returnsType(), Type.ATOMIC)) {
                  arg = new Atomize(context, arg);
              }
              steps.add(arg);
          }
          
          if (arguments.size() >= 3) {
              Expression arg = arguments.get(2);
              arg = new DynamicCardinalityCheck(context, Cardinality.EXACTLY_ONE, arg,
                      new Error(Error.FUNC_PARAM_CARDINALITY, "3", mySignature)); 
              if(!Type.subTypeOf(arg.returnsType(), Type.ATOMIC)) {
                  arg = new Atomize(context, arg);
              }
              steps.add(arg);
          }
  
          final List<LocationStep> steps = BasicExpressionVisitor.findLocationSteps(path);
          if (!steps.isEmpty()) {
              final LocationStep firstStep = steps.get(0);
              LocationStep lastStep = steps.get(steps.size() - 1);
              if (firstStep != null && lastStep != null) {
  	            final NodeTest test = lastStep.getTest();
  	            if (!test.isWildcardTest() && test.getName() != null) {
  
  	                if (lastStep.getAxis() == Constants.ATTRIBUTE_AXIS || lastStep.getAxis() == Constants.DESCENDANT_ATTRIBUTE_AXIS) {
                          contextQName = new QName(test.getName(), ElementValue.ATTRIBUTE);
                      } else {
                          contextQName = new QName(test.getName());
                      }
  	                contextStep = lastStep;
  	                axis = firstStep.getAxis();
  	                if (axis == Constants.SELF_AXIS && steps.size() > 1) {
  	                	if (steps.get(1) != null) {
  	                		axis = steps.get(1).getAxis();
  	                	} else {
                      		contextQName = null;
                      		contextStep = null;
                      		axis = Constants.UNKNOWN_AXIS;
  	                	}
  	                }
  	            }
              }
          }
      }
  
      public boolean canOptimize(Sequence contextSequence) {
          if (contextQName == null)
              {return false;}
          return Type.subTypeOf(Optimize.getQNameIndexType(context, contextSequence, contextQName), Type.STRING);
      }
  
      public boolean optimizeOnSelf() {
          return false;
      }
  
      public boolean optimizeOnChild() {
          return false;
      }
  
      public int getOptimizeAxis() {
          return axis;
      }
  
      @Override
      public NodeSet preSelect(Sequence contextSequence, boolean useContext) throws XPathException {
  