Coverage Report - org.jaxen.function.StringFunction

Classes in this File Line Coverage Branch Coverage Complexity
StringFunction
92% 
100% 
8.8

 1  
 /*
 2  
  * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/function/StringFunction.java,v 1.30 2005/09/14 13:03:59 elharo Exp $
 3  
  * $Revision: 1.30 $
 4  
  * $Date: 2005/09/14 13:03:59 $
 5  
  *
 6  
  * ====================================================================
 7  
  *
 8  
  * Copyright (C) 2000-2002 bob mcwhirter & James Strachan.
 9  
  * All rights reserved.
 10  
  *
 11  
  * Redistribution and use in source and binary forms, with or without
 12  
  * modification, are permitted provided that the following conditions
 13  
  * are met:
 14  
  * 
 15  
  * 1. Redistributions of source code must retain the above copyright
 16  
  *    notice, this list of conditions, and the following disclaimer.
 17  
  *
 18  
  * 2. Redistributions in binary form must reproduce the above copyright
 19  
  *    notice, this list of conditions, and the disclaimer that follows 
 20  
  *    these conditions in the documentation and/or other materials 
 21  
  *    provided with the distribution.
 22  
  *
 23  
  * 3. The name "Jaxen" must not be used to endorse or promote products
 24  
  *    derived from this software without prior written permission.  For
 25  
  *    written permission, please contact license@jaxen.org.
 26  
  * 
 27  
  * 4. Products derived from this software may not be called "Jaxen", nor
 28  
  *    may "Jaxen" appear in their name, without prior written permission
 29  
  *    from the Jaxen Project Management (pm@jaxen.org).
 30  
  * 
 31  
  * In addition, we request (but do not require) that you include in the 
 32  
  * end-user documentation provided with the redistribution and/or in the 
 33  
  * software itself an acknowledgement equivalent to the following:
 34  
  *     "This product includes software developed by the
 35  
  *      Jaxen Project <http://www.jaxen.org/>."
 36  
  * Alternatively, the acknowledgment may be graphical using the logos 
 37  
  * available at http://www.jaxen.org/
 38  
  *
 39  
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 40  
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 41  
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 42  
  * DISCLAIMED.  IN NO EVENT SHALL THE Jaxen AUTHORS OR THE PROJECT
 43  
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 44  
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 45  
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 46  
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 47  
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 48  
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 49  
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 50  
  * SUCH DAMAGE.
 51  
  *
 52  
  * ====================================================================
 53  
  * This software consists of voluntary contributions made by many 
 54  
  * individuals on behalf of the Jaxen Project and was originally 
 55  
  * created by bob mcwhirter <bob@werken.com> and 
 56  
  * James Strachan <jstrachan@apache.org>.  For more information on the 
 57  
  * Jaxen Project, please see <http://www.jaxen.org/>.
 58  
  * 
 59  
  * $Id: StringFunction.java,v 1.30 2005/09/14 13:03:59 elharo Exp $
 60  
  */
 61  
 
 62  
 
 63  
 package org.jaxen.function;
 64  
 
 65  
 import org.jaxen.Context;
 66  
 import org.jaxen.Function;
 67  
 import org.jaxen.FunctionCallException;
 68  
 import org.jaxen.Navigator;
 69  
 import org.jaxen.UnsupportedAxisException;
 70  
 import org.jaxen.JaxenRuntimeException;
 71  
 
 72  
 import java.text.DecimalFormat;
 73  
 import java.text.NumberFormat;
 74  
 import java.text.DecimalFormatSymbols;
 75  
 import java.util.List;
 76  
 import java.util.Iterator;
 77  
 import java.util.Locale;
 78  
 
 79  
 /**
 80  
  * <p>
 81  
  * <b>4.2</b> <code><i>string</i> string(<i>object</i>)</code>
 82  
  * </p>
 83  
  * 
 84  
  * 
 85  
  * <blockquote src="http://www.w3.org/TR/xpath">
 86  
  * <p>
 87  
  * The <b>string</b> function converts
 88  
  * an object to a string as follows:
 89  
  * </p>
 90  
  * 
 91  
  * <ul>
 92  
  * 
 93  
  * <li>
 94  
  * <p>
 95  
  * A node-set is converted to a string by returning the <a
 96  
  * href="http://www.w3.org/TR/xpath#dt-string-value" target="_top">string-value</a> of the node in the node-set
 97  
  * that is first in <a href="http://www.w3.org/TR/xpath#dt-document-order" target="_top">document order</a>. If
 98  
  * the node-set is empty, an empty string is returned.
 99  
  * </p>
 100  
  * </li>
 101  
  * 
 102  
  * <li>
 103  
  * <p>
 104  
  * A number is converted to a string as follows
 105  
  * </p>
 106  
  * 
 107  
  * <ul>
 108  
  * 
 109  
  * <li>
 110  
  * <p>
 111  
  * NaN is converted to the string <code>NaN</code>
 112  
  * </p>
 113  
  * </li>
 114  
  * 
 115  
  * <li>
 116  
  * <p>
 117  
  * positive zero is converted to the string <code>0</code>
 118  
  * </p>
 119  
  * </li>
 120  
  * 
 121  
  * <li>
 122  
  * 
 123  
  * <p>
 124  
  * negative zero is converted to the string <code>0</code>
 125  
  * </p>
 126  
  * </li>
 127  
  * 
 128  
  * <li>
 129  
  * <p>
 130  
  * positive infinity is converted to the string <code>Infinity</code>
 131  
  * </p>
 132  
  * </li>
 133  
  * 
 134  
  * <li>
 135  
  * <p>
 136  
  * negative infinity is converted to the string <code>-Infinity</code>
 137  
  * 
 138  
  * </p>
 139  
  * </li>
 140  
  * 
 141  
  * <li>
 142  
  * <p>
 143  
  * if the number is an integer, the number is represented in decimal
 144  
  * form as a <a href="http://www.w3.org/TR/xpath#NT-Number" target="_top">Number</a> with no decimal point and
 145  
  * no leading zeros, preceded by a minus sign (<code>-</code>) if
 146  
  * the number is negative
 147  
  * </p>
 148  
  * </li>
 149  
  * 
 150  
  * <li>
 151  
  * <p>
 152  
  * otherwise, the number is represented in decimal form as a Number including a decimal point with at least
 153  
  * one digit before the decimal point and at least one digit after the
 154  
  * decimal point, preceded by a minus sign (<code>-</code>) if the
 155  
  * number is negative; there must be no leading zeros before the decimal
 156  
  * point apart possibly from the one required digit immediately before
 157  
  * the decimal point; beyond the one required digit after the decimal
 158  
  * point there must be as many, but only as many, more digits as are
 159  
  * needed to uniquely distinguish the number from all other IEEE 754
 160  
  * numeric values.
 161  
  * </p>
 162  
  * 
 163  
  * </li>
 164  
  * 
 165  
  * </ul>
 166  
  * 
 167  
  * </li>
 168  
  * 
 169  
  * <li>
 170  
  * <p>
 171  
  * The boolean false value is converted to the string <code>false</code>.
 172  
  * The boolean true value is converted to the string <code>true</code>.
 173  
  * </p>
 174  
  * </li>
 175  
  * 
 176  
  * <li>
 177  
  * <p>
 178  
  * An object of a type other than the four basic types is converted to a
 179  
  * string in a way that is dependent on that type.
 180  
  * </p>
 181  
  * 
 182  
  * </li>
 183  
  * 
 184  
  * </ul>
 185  
  * 
 186  
  * <p>
 187  
  * If the argument is omitted, it defaults to a node-set with the
 188  
  * context node as its only member.
 189  
  * </p>
 190  
  * 
 191  
  * </blockquote>
 192  
  * 
 193  
  * @author bob mcwhirter (bob @ werken.com)
 194  
  * @see <a href="http://www.w3.org/TR/xpath#function-string"
 195  
  *      target="_top">Section 4.2 of the XPath Specification</a>
 196  
  */
 197  
 public class StringFunction implements Function
 198  
 {
 199  
 
 200  487
     private static DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
 201  
     
 202  
     static {
 203  487
         DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
 204  487
         symbols.setNaN("NaN");
 205  487
         symbols.setInfinity("Infinity");
 206  487
         format.setGroupingUsed(false);
 207  487
         format.setMaximumFractionDigits(32);
 208  487
         format.setDecimalFormatSymbols(symbols);
 209  487
     }
 210  
 
 211  
     /**
 212  
      * Create a new <code>StringFunction</code> object.
 213  
      */
 214  497
     public StringFunction() {}
 215  
     
 216  
     /**
 217  
      * Returns the string-value of  
 218  
      * <code>args.get(0)</code> or of the context node if ,code>args</code> is empty.
 219  
      * 
 220  
      * @param context the context at the point in the
 221  
      *         expression where the function is called
 222  
      * @param args list with zero or one element
 223  
      * 
 224  
      * @return a <code>String</code> 
 225  
      * 
 226  
      * @throws FunctionCallException if <code>args</code> has more than one item
 227  
      */    
 228  
     public Object call(Context context,
 229  
                        List args) throws FunctionCallException
 230  
     {
 231  310
         int size = args.size();
 232  
 
 233  310
         if ( size == 0 )
 234  
         {
 235  200
             return evaluate( context.getNodeSet(),
 236  
                              context.getNavigator() );
 237  
         }
 238  110
         else if ( size == 1 )
 239  
         {
 240  100
             return evaluate( args.get(0),
 241  
                              context.getNavigator() );
 242  
         }
 243  
 
 244  10
         throw new FunctionCallException( "string() takes at most argument." );
 245  
     }
 246  
     
 247  
     /**
 248  
      * Returns the string-value of <code>obj</code>.
 249  
      * 
 250  
      * @param obj the object whose string-value is calculated
 251  
      * @param nav the navigator used to calculate the string-value
 252  
      * 
 253  
      * @return a <code>String</code>. May be empty but is never null.
 254  
      */    
 255  
     public static String evaluate(Object obj,
 256  
                                   Navigator nav)
 257  
     {
 258  
         try
 259  
         {
 260  56486
             if (obj == null) {
 261  0
                 return "";
 262  
             }
 263  
             
 264  
             // Workaround because XOM uses lists for Text nodes
 265  
             // so we need to check for that first
 266  56486
             if (nav != null && nav.isText(obj))
 267  
             {
 268  240
                 return nav.getTextStringValue(obj);
 269  
             }
 270  
             
 271  56246
             if (obj instanceof List)
 272  
             {
 273  2650
                 List list = (List) obj;
 274  2650
                 if (list.isEmpty())
 275  
                 {
 276  80
                     return "";
 277  
                 }
 278  
                 // do not recurse: only first list should unwrap
 279  2570
                 obj = list.get(0);
 280  
             }
 281  
             
 282  56166
             if (nav != null) {
 283  
                 // This stack of instanceof really suggests there's 
 284  
                 // a failure to take advantage of polymorphism here
 285  56166
                 if (nav.isElement(obj))
 286  
                 {
 287  5240
                     return nav.getElementStringValue(obj);
 288  
                 }
 289  50926
                 else if (nav.isAttribute(obj))
 290  
                 {
 291  10300
                     return nav.getAttributeStringValue(obj);
 292  
                 }
 293  
     
 294  40626
                 else if (nav.isDocument(obj))
 295  
                 {
 296  80
                     Iterator childAxisIterator = nav.getChildAxisIterator(obj);
 297  80
                     while (childAxisIterator.hasNext())
 298  
                     {
 299  80
                         Object descendant = childAxisIterator.next();
 300  80
                         if (nav.isElement(descendant))
 301  
                         {
 302  80
                             return nav.getElementStringValue(descendant);
 303  
                         }
 304  
                     }
 305  
                 }
 306  40546
                 else if (nav.isProcessingInstruction(obj))
 307  
                 {
 308  120
                     return nav.getProcessingInstructionData(obj);
 309  
                 }
 310  40426
                 else if (nav.isComment(obj))
 311  
                 {
 312  10
                     return nav.getCommentStringValue(obj);
 313  
                 }
 314  40416
                 else if (nav.isText(obj))
 315  
                 {
 316  200
                     return nav.getTextStringValue(obj);
 317  
                 }
 318  40216
                 else if (nav.isNamespace(obj))
 319  
                 {
 320  10
                     return nav.getNamespaceStringValue(obj);
 321  
                 }
 322  
             }
 323  
             
 324  40206
             if (obj instanceof String)
 325  
             {
 326  37680
                 return (String) obj;
 327  
             }
 328  2526
             else if (obj instanceof Boolean)
 329  
             {
 330  1360
                 return stringValue(((Boolean) obj).booleanValue());
 331  
             }
 332  1166
             else if (obj instanceof Number)
 333  
             {
 334  1166
                 return stringValue(((Number) obj).doubleValue());
 335  
             }
 336  
             
 337  
         }
 338  0
         catch (UnsupportedAxisException e)
 339  
         {
 340  0
             throw new JaxenRuntimeException(e);
 341  0
         }
 342  
         
 343  0
         return "";
 344  
 
 345  
     }
 346  
 
 347  
     private static String stringValue(double value)
 348  
     {
 349  
         
 350  
         // DecimalFormat formats negative zero as "-0".
 351  
         // Therefore we need to test for zero explicitly here.
 352  1166
         if (value == 0) return "0";
 353  
         
 354  
         // need to synchronize object for thread-safety
 355  1084
         String result = null;
 356  1084
         synchronized (format) {
 357  1084
             result = format.format(value);
 358  1084
         }
 359  1084
         return result;
 360  
         
 361  
     }
 362  
 
 363  
     private static String stringValue(boolean value)
 364  
     {
 365  1360
         return value ? "true" : "false";
 366  
     }
 367  
 
 368  
 }