Coverage Report - net.sf.practicalxml.xpath.XPathWrapperFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
XPathWrapperFactory
100%
49/49
100%
17/17
1.857
XPathWrapperFactory$1
100%
2/2
N/A
1.857
XPathWrapperFactory$2
100%
1/1
N/A
1.857
XPathWrapperFactory$CacheType
100%
4/4
N/A
1.857
 
 1  
 // Copyright 2008-2014 severally by the contributors
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package net.sf.practicalxml.xpath;
 16  
 
 17  
 import java.util.HashMap;
 18  
 import java.util.Map;
 19  
 
 20  
 import javax.xml.namespace.QName;
 21  
 import javax.xml.xpath.XPathFunction;
 22  
 
 23  
 
 24  
 /**
 25  
  *  A factory for producing {@link XPathWrapper} instances with the same
 26  
  *  configuration (namespace bindings and functions), reducing the amount
 27  
  *  of code needed when applying multiple expressions to the same complex
 28  
  *  XML.
 29  
  *  <p>
 30  
  *  As with <code>XPathWrapper</code> itself, this object is intended to
 31  
  *  be configured using the builder pattern: all configuration methods
 32  
  *  return the factory instance. Binding methods are the same as
 33  
  *  <code>XPathWrapper</code>, excepting the deprecated
 34  
  *  <code>bindDefaultNamespace()</code>.
 35  
  *  <p>
 36  
  *  As a potential performance improvement, for XPath expressions that are
 37  
  *  repeatedly executed, you can create an optional cache for created wrapper
 38  
  *  instances. Note that changes to the factory configuration will not affect
 39  
  *  cached wrapper instances. <em>Note:</em> At the present time, there is no
 40  
  *  way to clear the cache.
 41  
  *  <p>
 42  
  *  The {@link #newXPath} method is reentrant, and may be invoked from multiple
 43  
  *  threads concurrently. However, this class is not generally thread-safe: you
 44  
  *  cannot configure in one thread and create wrapper instances in another.
 45  
  *
 46  
  *  @since 1.1.12
 47  
  */
 48  
 public class XPathWrapperFactory
 49  
 implements Cloneable
 50  
 {
 51  
     /**
 52  
      *  Defines the various options for caching.
 53  
      */
 54  5
     public enum CacheType
 55  
     {
 56  
         /**
 57  
          *  No cache. Each call to to <code>newXPath()</code> produces a new
 58  
          *  instance.
 59  
          */
 60  1
         NONE,
 61  
 
 62  
         /**
 63  
          *  A simple cache, keyed by XPath expression. This should <em>not</em>
 64  
          *  be used in a multi-threaded application.
 65  
          */
 66  1
         SIMPLE,
 67  
 
 68  
         /**
 69  
          *  A thread-local cache, keyed by XPath expression.
 70  
          */
 71  1
         THREADSAFE
 72  
     }
 73  
 
 74  
 
 75  
 //----------------------------------------------------------------------------
 76  
 //  Instance Variables and Constructors
 77  
 //----------------------------------------------------------------------------
 78  
 
 79  
     // only one of these will be not-null, and only if we use that cache type
 80  
     private Map<String,XPathWrapper> _simpleCache;
 81  
     private ThreadLocal<Map<String,XPathWrapper>> _threadsafeCache;
 82  
 
 83  
     // these will be shared between paths
 84  8
     private Map<String,String> _namespaces = new HashMap<String,String>();
 85  8
     private Map<QName,Object> _variables = new HashMap<QName,Object>();
 86  
 
 87  
     // this will be copied for each path
 88  8
     private FunctionResolver _functions = new FunctionResolver();
 89  
 
 90  
 
 91  
     /**
 92  
      *  Creates a factory that will always return new <code>XPathWrapper</code>
 93  
      *  instances.
 94  
      */
 95  
     public XPathWrapperFactory()
 96  
     {
 97  4
         this(CacheType.NONE);
 98  4
     }
 99  
 
 100  
 
 101  
     /**
 102  
      *  Creates a factory that will optionally store <code>XPathWrapper</code>
 103  
      *  instances in a cache.
 104  
      *
 105  
      *  @param  cacheType   One of the {@link XPathWrapperFactory.CacheType} values.
 106  
      */
 107  
     public XPathWrapperFactory(CacheType cacheType)
 108  8
     {
 109  8
         switch (cacheType)
 110  
         {
 111  
             case SIMPLE :
 112  2
                 _simpleCache = new HashMap<String,XPathWrapper>();
 113  2
                 break;
 114  
             case THREADSAFE :
 115  1
                 _threadsafeCache = new ThreadLocal<Map<String,XPathWrapper>>()
 116  3
                 {
 117  
                     @Override
 118  
                     protected Map<String,XPathWrapper> initialValue()
 119  
                     {
 120  2
                         return new HashMap<String,XPathWrapper>();
 121  
                     }
 122  
                 };
 123  1
                 break;
 124  
             default :
 125  
                 // no cache, don't do anything
 126  
         }
 127  8
     }
 128  
 
 129  
 
 130  
 //----------------------------------------------------------------------------
 131  
 //  Public methods
 132  
 //----------------------------------------------------------------------------
 133  
 
 134  
     /**
 135  
      *  Adds a namespace binding, which will be used for subsequent wrappers
 136  
      *  created from this factory. If you add multiple bindings with the same
 137  
      *  namespace, only the last is retained.
 138  
      *
 139  
      *  @param  prefix  The prefix used to reference this namespace in the
 140  
      *                  XPath expression. Note that this does <em>not</em>
 141  
      *                  need to be the same prefix used by the document.
 142  
      *  @param  nsURI   The namespace URI to associate with this prefix.
 143  
      *
 144  
      *  @return The wrapper, so that calls may be chained.
 145  
      */
 146  
     public XPathWrapperFactory bindNamespace(String prefix, String nsURI)
 147  
     {
 148  5
         _namespaces.put(prefix, nsURI);
 149  5
         return this;
 150  
     }
 151  
 
 152  
 
 153  
     /**
 154  
      *  Binds a value to a variable, replacing any previous value for that
 155  
      *  variable. Unlike other configuration methods, this may be called
 156  
      *  after calling <code>evaluate()</code>; the new values will be used
 157  
      *  for subsequent evaluations.
 158  
      *
 159  
      *  @param  name    The name of the variable; this is turned into a
 160  
      *                  <code>QName</code> without namespace.
 161  
      *  @param  value   The value of the variable; the XPath evaluator must
 162  
      *                  be able to convert this value into a type usable in
 163  
      *                  an XPath expression.
 164  
      *
 165  
      *  @return The wrapper, so that calls may be chained.
 166  
      */
 167  
     public XPathWrapperFactory bindVariable(String name, Object value)
 168  
     {
 169  5
         return bindVariable(new QName(name), value);
 170  
     }
 171  
 
 172  
 
 173  
     /**
 174  
      *  Binds a value to a variable, replacing any previous value for that
 175  
      *  variable. Unlike other configuration methods, this may be called
 176  
      *  after calling <code>evaluate()</code>; the new values will be used
 177  
      *  for subsequent evaluations.
 178  
      *
 179  
      *  @param  name    The fully-qualified name of the variable.
 180  
      *  @param  value   The value of the variable; the XPath evaluator must
 181  
      *                  be able to convert this value into a type usable in
 182  
      *                  an XPath expression.
 183  
      *
 184  
      *  @return The wrapper, so that calls may be chained.
 185  
      */
 186  
     public XPathWrapperFactory bindVariable(QName name, Object value)
 187  
     {
 188  6
         _variables.put(name, value);
 189  6
         return this;
 190  
     }
 191  
 
 192  
 
 193  
     /**
 194  
      *  Binds a self-describing function to this expression. Subsequent calls
 195  
      *  with the same name/arity will silently replace the binding.
 196  
      *  <p>
 197  
      *  All XPath instances created from this factory will share a single
 198  
      *  instance of the bound function. If your function maintains state,
 199  
      *  you are responsible for proper synchronization.
 200  
      *  <p>
 201  
      *  Per the JDK documentation, user-defined functions must occupy their
 202  
      *  own namespace. You must bind a namespace for this function, and use
 203  
      *  the bound prefix to reference it in your expression. Alternatively,
 204  
      *  you can call the variant of <code>bindFunction()</code> that binds
 205  
      *  a prefix to the function's namespace.
 206  
      *
 207  
      *  @param  func    The function.
 208  
      *
 209  
      *  @return The wrapper, so that calls may be chained.
 210  
      */
 211  
     public XPathWrapperFactory bindFunction(FunctionResolver.SelfDescribingFunction func)
 212  
     {
 213  2
         _functions.addFunction(func);
 214  2
         return this;
 215  
     }
 216  
 
 217  
 
 218  
     /**
 219  
      *  Binds a self-describing function to this expression, along with the
 220  
      *  prefix used to access that function. This also establishes a namespace
 221  
      *  binding for that prefix.
 222  
      *  <p>
 223  
      *  All XPath instances created from this factory will share a single
 224  
      *  instance of the bound function. If your function maintains state,
 225  
      *  you are responsible for proper synchronization.
 226  
      *  <p>
 227  
      *  Per the JDK documentation, user-defined functions must occupy their
 228  
      *  own namespace. This method will retrieve the namespace from the
 229  
      *  function, and bind it to the passed prefix.
 230  
      *
 231  
      *  @param  func    The function.
 232  
      *  @param  prefix  The prefix to bind to this function's namespace.
 233  
      *
 234  
      *  @return The wrapper, so that calls may be chained.
 235  
      */
 236  
     public XPathWrapperFactory bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix)
 237  
     {
 238  1
         _functions.addFunction(func);
 239  1
         bindNamespace(prefix, func.getNamespaceUri());
 240  1
         return this;
 241  
     }
 242  
 
 243  
 
 244  
     /**
 245  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 246  
      *  handling any number of arguments. Subsequent calls to this method
 247  
      *  with the same name will silently replace the binding.
 248  
      *  <p>
 249  
      *  All XPath instances created from this factory will share a single
 250  
      *  instance of the bound function. If your function maintains state,
 251  
      *  you are responsible for proper synchronization.
 252  
      *  <p>
 253  
      *  Per the JDK documentation, user-defined functions must occupy their
 254  
      *  own namespace. If the qualified name that you pass to this method
 255  
      *  includes a prefix, the associated namespace will be bound to that
 256  
      *  prefix. If not, you must bind the namespace explicitly. In either
 257  
      *  case, you must refer to the function in your expression using a
 258  
      *  bound prefix.
 259  
      *
 260  
      *  @param  name    The qualified name for this function. Must contain
 261  
      *                  a name and namespace, may contain a prefix.
 262  
      *  @param  func    The function to bind to this name.
 263  
      *
 264  
      *  @return The wrapper, so that calls may be chained.
 265  
      */
 266  
     public XPathWrapperFactory bindFunction(QName name, XPathFunction func)
 267  
     {
 268  1
         _functions.addFunction(func, name);
 269  1
         return this;
 270  
     }
 271  
 
 272  
 
 273  
     /**
 274  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 275  
      *  handling a specific number of arguments. Subsequent calls to this
 276  
      *  method with the same name and arity will silently replace the binding.
 277  
      *  <p>
 278  
      *  All XPath instances created from this factory will share a single
 279  
      *  instance of the bound function. If your function maintains state,
 280  
      *  you are responsible for proper synchronization.
 281  
      *  <p>
 282  
      *  Per the JDK documentation, user-defined functions must occupy their
 283  
      *  own namespace. If the qualified name that you pass to this method
 284  
      *  includes a prefix, the associated namespace will be bound to that
 285  
      *  prefix. If not, you must bind the namespace explicitly. In either
 286  
      *  case, you must refer to the function in your expression using a
 287  
      *  bound prefix.
 288  
      *
 289  
      *  @param  name    The qualified name for this function. Must contain
 290  
      *                  a name and namespace, may contain a prefix.
 291  
      *  @param  func    The function to bind to this name.
 292  
      *  @param  arity   The number of arguments accepted by this function.
 293  
      *
 294  
      *  @return The wrapper, so that calls may be chained.
 295  
      */
 296  
     public XPathWrapperFactory bindFunction(QName name, XPathFunction func, int arity)
 297  
     {
 298  1
         _functions.addFunction(func, name, arity);
 299  1
         return this;
 300  
     }
 301  
 
 302  
 
 303  
     /**
 304  
      *  Binds a standard <code>XPathFunction</code> to this expression,
 305  
      *  handling a specific range of arguments. Subsequent calls to this
 306  
      *  method with the same name and range of arguments will silently
 307  
      *  replace the binding.
 308  
      *  <p>
 309  
      *  All XPath instances created from this factory will share a single
 310  
      *  instance of the bound function. If your function maintains state,
 311  
      *  you are responsible for proper synchronization.
 312  
      *  <p>
 313  
      *  Per the JDK documentation, user-defined functions must occupy their
 314  
      *  own namespace. If the qualified name that you pass to this method
 315  
      *  includes a prefix, the associated namespace will be bound to that
 316  
      *  prefix. If not, you must bind the namespace explicitly. In either
 317  
      *  case, you must refer to the function in your expression using a
 318  
      *  bound prefix.
 319  
      *
 320  
      *  @param  name      The qualified name for this function. Must contain
 321  
      *                    a name and namespace, may contain a prefix.
 322  
      *  @param  func      The function to bind to this name.
 323  
      *  @param  minArity  The minimum number of arguments accepted by this
 324  
      *                    function.
 325  
      *  @param  maxArity  The maximum number of arguments accepted by this
 326  
      *                    function.
 327  
      *
 328  
      *  @return The wrapper, so that calls may be chained.
 329  
      */
 330  
     public XPathWrapperFactory bindFunction(
 331  
             QName name, XPathFunction func, int minArity, int maxArity)
 332  
     {
 333  1
         _functions.addFunction(func, name, minArity, maxArity);
 334  1
         return this;
 335  
     }
 336  
 
 337  
 
 338  
     /**
 339  
      *  Returns an {@link XPathWrapper} instance for the specified expression.
 340  
      *  This may either be a new or cached instance.
 341  
      */
 342  
     public XPathWrapper newXPath(String xpath)
 343  
     {
 344  23
         XPathWrapper wrapper = retrieveFromCache(xpath);
 345  23
         if (wrapper != null)
 346  3
             return wrapper;
 347  
 
 348  20
         wrapper = new XPathWrapper(xpath);
 349  
 
 350  20
         for (Map.Entry<String,String> namespace : _namespaces.entrySet())
 351  22
             wrapper.bindNamespace(namespace.getKey(), namespace.getValue());
 352  
 
 353  20
         for (Map.Entry<QName,Object> variable : _variables.entrySet())
 354  13
             wrapper.bindVariable(variable.getKey(), variable.getValue());
 355  
 
 356  20
         wrapper.setFunctionResolver(_functions.clone());
 357  
 
 358  20
         addToCache(xpath, wrapper);
 359  20
         return wrapper;
 360  
     }
 361  
 
 362  
 
 363  
 //----------------------------------------------------------------------------
 364  
 //  Internals
 365  
 //----------------------------------------------------------------------------
 366  
 
 367  
     private XPathWrapper retrieveFromCache(String xpath)
 368  
     {
 369  23
         if (_simpleCache != null)
 370  
         {
 371  4
             return _simpleCache.get(xpath);
 372  
         }
 373  19
         else if (_threadsafeCache != null)
 374  
         {
 375  3
             return _threadsafeCache.get().get(xpath);
 376  
         }
 377  
 
 378  16
         return null;
 379  
     }
 380  
 
 381  
 
 382  
     private void addToCache(String xpath, XPathWrapper wrapper)
 383  
     {
 384  20
         if (_simpleCache != null)
 385  
         {
 386  2
             _simpleCache.put(xpath, wrapper);
 387  
         }
 388  18
         else if (_threadsafeCache != null)
 389  
         {
 390  2
             _threadsafeCache.get().put(xpath, wrapper);
 391  
         }
 392  20
     }
 393  
 }