Coverage Report - com.damnhandy.uri.template.Expression
 
Classes in this File Line Coverage Branch Coverage Complexity
Expression
69%
64/92
42%
17/40
2.348
Expression$Builder
100%
8/8
100%
2/2
2.348
 
 1  
 /*
 2  
  * Copyright 2012, Ryan J. McDonough
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package com.damnhandy.uri.template;
 17  
 
 18  
 
 19  
 import com.damnhandy.uri.template.impl.Modifier;
 20  
 import com.damnhandy.uri.template.impl.Operator;
 21  
 import com.damnhandy.uri.template.impl.VarSpec;
 22  
 
 23  
 import java.util.ArrayList;
 24  
 import java.util.List;
 25  
 import java.util.regex.Matcher;
 26  
 import java.util.regex.Pattern;
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * An Expression represents the text between '{' and '}', including the enclosing
 31  
  * braces, as defined in <a href="ietf.org/html/rfc6570#section-2">Section 2 of RFC6570</a>.
 32  
  * </p>
 33  
  * <pre>
 34  
  * http://www.example.com/foo{?query,number}
 35  
  * \___________/
 36  
  * ^
 37  
  * |
 38  
  * |
 39  
  * The expression
 40  
  * </pre>
 41  
  * <p>
 42  
  * This class models this representation and adds helper functions for replacement and reverse matching.
 43  
  * </p>
 44  
  *
 45  
  * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a>
 46  
  * @version $Revision: 1.1 $
 47  
  * @since 2.0
 48  
  */
 49  
 public class Expression extends UriTemplateComponent
 50  
 {
 51  
 
 52  
     /**
 53  
      * The serialVersionUID
 54  
      */
 55  
     private static final long serialVersionUID = -5305648325957481840L;
 56  
 
 57  
     /**
 58  
      * A regex quoted string that matches the expression for replacement in the
 59  
      * expansion process. For example:
 60  
      * <pre>
 61  
      *          \Q{?query,number}\E
 62  
      * </pre>
 63  
      */
 64  
     private String replacementPattern;
 65  
 
 66  
     /**
 67  
      * That {@link Operator} value that is associated with this Expression
 68  
      */
 69  
     private Operator op;
 70  
 
 71  
     /**
 72  
      * The character position where this expression occurs in the URI template
 73  
      */
 74  
     private final int location;
 75  
     /**
 76  
      * The the parsed {@link VarSpec} instances found in the expression.
 77  
      */
 78  
     private List<VarSpec> varSpecs;
 79  
 
 80  
     /**
 81  
      * The Patter that would be used to reverse match this expression
 82  
      */
 83  
     private Pattern matchPattern;
 84  
 
 85  
 
 86  
     /**
 87  
      * Creates a new {@link Builder} to create a simple expression according
 88  
      * to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.2">3.2.2</a>.
 89  
      * Calling:
 90  
      * <pre>
 91  
      * String exp = Expression.simple(var("var")).build().toString();
 92  
      * </pre>
 93  
      * <p>
 94  
      * Will return the following expression:
 95  
      * </p>
 96  
      * <pre>
 97  
      * {var}
 98  
      * </pre>
 99  
      *
 100  
      * @return
 101  
      */
 102  
     public static Builder simple(VarSpec... varSpec)
 103  
     {
 104  12
         return Builder.create(Operator.NUL, varSpec);
 105  
     }
 106  
 
 107  
 
 108  
     /**
 109  
      * Creates a new {@link Builder} to create an expression that will use reserved expansion
 110  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.3">3.2.3</a>.
 111  
      * Calling:
 112  
      * <pre>
 113  
      * String exp = Expression.reserved().var("var").build().toString();
 114  
      * </pre>
 115  
      * <p>
 116  
      * Will return the following expression:
 117  
      * </p>
 118  
      * <pre>
 119  
      * {+var}
 120  
      * </pre>
 121  
      *
 122  
      * @return
 123  
      */
 124  
     public static Builder reserved(VarSpec... varSpec)
 125  
     {
 126  14
         return Builder.create(Operator.RESERVED, varSpec);
 127  
     }
 128  
 
 129  
     /**
 130  
      * Creates a new {@link Builder} to create an expression with a fragment operator
 131  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.4">3.2.4</a>.
 132  
      * Calling:
 133  
      * <pre>
 134  
      * String exp = Expression.fragment().var("var").build().toString();
 135  
      * </pre>
 136  
      * <p>
 137  
      * Will return the following expression:
 138  
      * </p>
 139  
      * <pre>
 140  
      * {#var}
 141  
      * </pre>
 142  
      *
 143  
      * @return
 144  
      */
 145  
     public static Builder fragment(VarSpec... varSpec)
 146  
     {
 147  20
         return Builder.create(Operator.FRAGMENT, varSpec);
 148  
     }
 149  
 
 150  
     /**
 151  
      * Creates a new {@link Builder} to create an expression using label expansion
 152  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.5">3.2.5</a>.
 153  
      * Calling:
 154  
      * <pre>
 155  
      * String exp = Expression.label(var("var")).build().toString();
 156  
      * </pre>
 157  
      * <p>
 158  
      * Will return the following expression:
 159  
      * </p>
 160  
      * <pre>
 161  
      * {.var}
 162  
      * </pre>
 163  
      *
 164  
      * @return
 165  
      */
 166  
     public static Builder label(VarSpec... varSpec)
 167  
     {
 168  8
         return Builder.create(Operator.NAME_LABEL, varSpec);
 169  
     }
 170  
 
 171  
     /**
 172  
      * Creates a new {@link Builder} to create an expression using path expansion
 173  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.6">3.2.6</a>.
 174  
      * Calling:
 175  
      * <pre>
 176  
      * String exp = Expression.path().var("var").build().toString();
 177  
      * </pre>
 178  
      * <p>
 179  
      * Will return the following expression:
 180  
      * </p>
 181  
      * <pre>
 182  
      * {/var}
 183  
      * </pre>
 184  
      *
 185  
      * @return
 186  
      */
 187  
     public static Builder path(VarSpec... varSpec)
 188  
     {
 189  28
         return Builder.create(Operator.PATH, varSpec);
 190  
     }
 191  
 
 192  
     /**
 193  
      * Creates a new {@link Builder} to create an expression using path-style parameter
 194  
      * (a.k.a. matrix parameter) expansion according to
 195  
      * section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.7">3.2.7</a>.
 196  
      * Calling:
 197  
      * <pre>
 198  
      * String exp = Expression.matrix().var("var").build().toString();
 199  
      * </pre>
 200  
      * <p>
 201  
      * Will return the following expression:
 202  
      * </p>
 203  
      * <pre>
 204  
      * {;var}
 205  
      * </pre>
 206  
      *
 207  
      * @return
 208  
      */
 209  
     public static Builder matrix(VarSpec... varSpec)
 210  
     {
 211  4
         return Builder.create(Operator.MATRIX, varSpec);
 212  
     }
 213  
 
 214  
     /**
 215  
      * Creates a new {@link Builder} to create an expression using form-style query string expansion
 216  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.8">3.2.8</a>.
 217  
      * Calling:
 218  
      * <pre>
 219  
      * String exp = Expression.query().var("var").build().toString();
 220  
      * </pre>
 221  
      * <p>
 222  
      * Will return the following expression:
 223  
      * </p>
 224  
      * <pre>
 225  
      * {?var}
 226  
      * </pre>
 227  
      *
 228  
      * @return
 229  
      */
 230  
     public static Builder query(VarSpec... varSpec)
 231  
     {
 232  18
         return Builder.create(Operator.QUERY, varSpec);
 233  
     }
 234  
 
 235  
     /**
 236  
      * Creates a new {@link Builder} to create an expression using form-style query continuation expansion
 237  
      * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.9">3.2.9</a>.
 238  
      * Calling:
 239  
      * <pre>
 240  
      * String exp = Expression.continuation().var("var").build().toString();
 241  
      * </pre>
 242  
      * <p>
 243  
      * Will return the following expression:
 244  
      * </p>
 245  
      * <pre>
 246  
      * {&var}
 247  
      * </pre>
 248  
      *
 249  
      * @return
 250  
      */
 251  
     public static Builder continuation(VarSpec... varSpec)
 252  
     {
 253  8
         return Builder.create(Operator.CONTINUATION, varSpec);
 254  
     }
 255  
 
 256  
 
 257  
     /**
 258  
      * Create a new Expression.
 259  
      *
 260  
      * @param rawExpression
 261  
      * @param startPosition
 262  
      * @throws MalformedUriTemplateException
 263  
      */
 264  
     public Expression(final String rawExpression, final int startPosition) throws MalformedUriTemplateException
 265  
     {
 266  1052
         super(startPosition);
 267  1052
         this.location = startPosition;
 268  1052
         this.parseRawExpression(rawExpression);
 269  1004
     }
 270  
 
 271  
     /**
 272  
      * Create a new Expression
 273  
      *
 274  
      * @param op
 275  
      * @param varSpecs
 276  
      * @throws MalformedUriTemplateException
 277  
      */
 278  
     public Expression(final Operator op, final List<VarSpec> varSpecs)
 279  
     {
 280  112
         super(0);
 281  112
         this.op = op;
 282  112
         this.varSpecs = varSpecs;
 283  112
         this.replacementPattern = Pattern.quote(toString());
 284  112
         this.location = 0;
 285  112
     }
 286  
 
 287  
     /**
 288  
      * @param rawExpression
 289  
      * @throws MalformedUriTemplateException
 290  
      */
 291  
     private void parseRawExpression(String rawExpression) throws MalformedUriTemplateException
 292  
     {
 293  1052
         final String expressionReplacement = Pattern.quote(rawExpression);
 294  1052
         String token = rawExpression.substring(1, rawExpression.length() - 1);
 295  
         // Check for URI operators
 296  1052
         Operator operator = Operator.NUL;
 297  1052
         String firstChar = token.substring(0, 1);
 298  1052
         if (UriTemplate.containsOperator(firstChar))
 299  
         {
 300  
             try
 301  
             {
 302  824
                 operator = Operator.fromOpCode(firstChar);
 303  
             }
 304  4
             catch (IllegalArgumentException e)
 305  
             {
 306  4
                 throw new MalformedUriTemplateException("Invalid operator", this.location, e);
 307  820
             }
 308  820
             token = token.substring(1, token.length());
 309  
         }
 310  1048
         String[] varspecStrings = token.split(",");
 311  1048
         List<VarSpec> varspecs = new ArrayList<VarSpec>();
 312  
 
 313  2416
         for (String varname : varspecStrings)
 314  
         {
 315  1412
             int subStrPos = varname.indexOf(Modifier.PREFIX.getValue());
 316  
          /*
 317  
           * Prefix variable
 318  
           */
 319  1412
             if (subStrPos > 0)
 320  
             {
 321  102
                 String[] pair = varname.split(Modifier.PREFIX.getValue());
 322  
                 try
 323  
                 {
 324  102
                     Integer pos = Integer.valueOf(varname.substring(subStrPos + 1));
 325  94
                     varspecs.add(new VarSpec(pair[0], Modifier.PREFIX, pos));
 326  
                 }
 327  8
                 catch (NumberFormatException e)
 328  
                 {
 329  8
                     throw new MalformedUriTemplateException("The prefix value for " + pair[0] + " was not a number", this.location, e);
 330  94
                 }
 331  94
             }
 332  
 
 333  
          /*
 334  
           * Variable will be exploded
 335  
           */
 336  1310
             else if (varname.lastIndexOf(Modifier.EXPLODE.getValue()) > 0)
 337  
             {
 338  248
                 varspecs.add(new VarSpec(varname, Modifier.EXPLODE));
 339  
             }
 340  
          /*
 341  
           * Standard Value
 342  
           */
 343  
             else
 344  
             {
 345  1062
                 varspecs.add(new VarSpec(varname, Modifier.NONE));
 346  
             }
 347  
         }
 348  1004
         this.replacementPattern = expressionReplacement;
 349  1004
         this.op = operator;
 350  1004
         this.varSpecs = varspecs;
 351  1004
     }
 352  
 
 353  
 
 354  
 
 355  
 
 356  
     private Pattern buildMatchingPattern()
 357  
     {
 358  0
         StringBuilder b = new StringBuilder();
 359  0
         for (VarSpec v : getVarSpecs())
 360  
         {
 361  0
             b.append("(?<").append(v.getVariableName()).append(">[^\\/]+)");
 362  0
         }
 363  0
         return Pattern.compile(b.toString());
 364  
     }
 365  
 
 366  
     /**
 367  
      * Returns a string that contains a regular expression that matches the
 368  
      * URI template expression.
 369  
      *
 370  
      * @return the match pattern
 371  
      */
 372  
     @Override
 373  
     public Pattern getMatchPattern()
 374  
     {
 375  0
         if (this.matchPattern == null)
 376  
         {
 377  0
             this.matchPattern = buildMatchingPattern();
 378  
         }
 379  0
         return this.matchPattern;
 380  
     }
 381  
 
 382  
     /**
 383  
      * Get the replacementToken.
 384  
      *
 385  
      * @return the replacementToken.
 386  
      */
 387  
     public String getReplacementPattern()
 388  
     {
 389  594
         return replacementPattern;
 390  
     }
 391  
 
 392  
     /**
 393  
      * Get the {@link Operator} value for this expression.
 394  
      *
 395  
      * @return the operator value.
 396  
      */
 397  
     public Operator getOperator()
 398  
     {
 399  1886
         return op;
 400  
     }
 401  
 
 402  
     /**
 403  
      * Get the varSpecs.
 404  
      *
 405  
      * @return the varSpecs.
 406  
      */
 407  
     public List<VarSpec> getVarSpecs()
 408  
     {
 409  660
         return varSpecs;
 410  
     }
 411  
 
 412  
     /**
 413  
      * Returns the string representation of the expression.
 414  
      *
 415  
      * @see Object#toString()
 416  
      */
 417  
     public String toString()
 418  
     {
 419  416
         StringBuilder b = new StringBuilder();
 420  416
         b.append("{").append(this.getOperator().getOperator());
 421  928
         for (int i = 0; i < varSpecs.size(); i++)
 422  
         {
 423  512
             VarSpec v = varSpecs.get(i);
 424  512
             b.append(v.getValue());
 425  
             // Double check that the value doesn't already contain the modifier
 426  512
             int r = v.getValue().lastIndexOf(v.getModifier().getValue());
 427  512
             if(v.getModifier() != null && v.getValue().lastIndexOf(v.getModifier().getValue()) == -1)
 428  
             {
 429  112
                 b.append(v.getModifier().getValue());
 430  
             }
 431  
 
 432  512
             if (v.getModifier() == Modifier.PREFIX)
 433  
             {
 434  64
                 b.append(v.getPosition());
 435  
             }
 436  512
             if (i != (varSpecs.size() - 1))
 437  
             {
 438  96
                 b.append(",");
 439  
             }
 440  
         }
 441  416
         return b.append("}").toString();
 442  
     }
 443  
 
 444  
     /**
 445  
      * Returns the value of this component
 446  
      *
 447  
      * @return the string value of this component.
 448  
      */
 449  
     @Override
 450  
     public String getValue()
 451  
     {
 452  280
         return toString();
 453  
     }
 454  
 
 455  
     @Override
 456  
     public int hashCode()
 457  
     {
 458  0
         final int prime = 31;
 459  0
         int result = 1;
 460  0
         result = prime * result + ((op == null) ? 0 : op.hashCode());
 461  0
         result = prime * result + ((varSpecs == null) ? 0 : varSpecs.hashCode());
 462  0
         return result;
 463  
     }
 464  
 
 465  
     @Override
 466  
     public boolean equals(Object obj)
 467  
     {
 468  0
         if (this == obj)
 469  
         {
 470  0
             return true;
 471  
         }
 472  0
         if (obj == null)
 473  
         {
 474  0
             return false;
 475  
         }
 476  0
         if (getClass() != obj.getClass())
 477  
         {
 478  0
             return false;
 479  
         }
 480  0
         Expression other = (Expression) obj;
 481  0
         if (op != other.op)
 482  
         {
 483  0
             return false;
 484  
         }
 485  0
         if (varSpecs == null)
 486  
         {
 487  0
             if (other.varSpecs != null)
 488  
             {
 489  0
                 return false;
 490  
             }
 491  
         }
 492  0
         else if (!varSpecs.equals(other.varSpecs))
 493  
         {
 494  0
             return false;
 495  
         }
 496  0
         return true;
 497  
     }
 498  
 
 499  
 
 500  
     /**
 501  
      * Builder class for creating an {@link Expression}.
 502  
      *
 503  
      * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a>
 504  
      * @version $Revision: 1.1 $
 505  
      */
 506  
     public static class Builder
 507  
     {
 508  
         /**
 509  
          *
 510  
          */
 511  
         private Operator operator;
 512  
 
 513  
         /**
 514  
          *
 515  
          */
 516  
         private List<VarSpec> varSpecs;
 517  
 
 518  
         /**
 519  
          * Create a new ExpressionBuilder.
 520  
          *
 521  
          * @param operator
 522  
          */
 523  
         private Builder(Operator operator, VarSpec... varSpec)
 524  112
         {
 525  112
             this.operator = operator;
 526  112
             this.varSpecs = new ArrayList<VarSpec>();
 527  236
             for (VarSpec v : varSpec)
 528  
             {
 529  124
                 varSpecs.add(v);
 530  
             }
 531  112
         }
 532  
 
 533  
         /**
 534  
          * @param operator
 535  
          * @return the builder
 536  
          */
 537  
         static Builder create(Operator operator, VarSpec... varSpec)
 538  
         {
 539  112
             return new Builder(operator, varSpec);
 540  
         }
 541  
 
 542  
 
 543  
         public Expression build()
 544  
         {
 545  112
             return new Expression(operator, varSpecs);
 546  
         }
 547  
     }
 548  
 
 549  
 }