Coverage Report - com.damnhandy.uri.template.UriTemplate
 
Classes in this File Line Coverage Branch Coverage Complexity
UriTemplate
89%
264/296
87%
191/218
3.933
UriTemplate$Encoding
100%
2/2
N/A
3.933
 
 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  
 import com.damnhandy.uri.template.impl.*;
 19  
 import org.joda.time.DateTime;
 20  
 import org.joda.time.format.DateTimeFormat;
 21  
 import org.joda.time.format.DateTimeFormatter;
 22  
 
 23  
 import java.io.Serializable;
 24  
 import java.io.UnsupportedEncodingException;
 25  
 import java.lang.reflect.Array;
 26  
 import java.text.DateFormat;
 27  
 import java.text.SimpleDateFormat;
 28  
 import java.util.*;
 29  
 import java.util.Map.Entry;
 30  
 import java.util.regex.Pattern;
 31  
 
 32  
 /**
 33  
  * <p>
 34  
  * This is the primary class for creating and manipulating URI templates. This project implements
 35  
  * <a href="http://tools.ietf.org/html/rfc6570">RFC6570 URI Templates</a> and produces output
 36  
  * that is compliant with the spec. The template processor supports <a href="http://tools.ietf.org/html/rfc6570#section-2.0">levels
 37  
  * 1 through 4</a>. In addition to supporting {@link Map}
 38  
  * and {@link List} values as composite types, the library also supports the use of Java objects
 39  
  * as well. Please see the {@link VarExploder} and {@link DefaultVarExploder} for more info.
 40  
  * </p>
 41  
  * <h3>Basic Usage:</h3>
 42  
  * <p>
 43  
  * There are many ways to use this library. The simplest way is to create a template from a
 44  
  * URI template  string:
 45  
  * </p>
 46  
  * <pre>
 47  
  * UriTemplate template = UriTemplate.fromTemplate("http://example.com/search{?q,lang}");
 48  
  * </pre>
 49  
  * <p>
 50  
  * Replacement values are added by calling the {@link #set(String, Object)} method on the template:
 51  
  * </p>
 52  
  * <pre>
 53  
  * template.set("q","cat")
 54  
  *         .set("lang","en");
 55  
  * String uri = template.expand();
 56  
  * </pre>
 57  
  * <p>The {@link #expand()} method will replace the variable names with the supplied values
 58  
  * and return the following URI:</p>
 59  
  * <pre>
 60  
  * http://example.com/search?q=cat&lang=en
 61  
  * </pre>
 62  
  *
 63  
  *
 64  
  * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a>
 65  
  * @version $Revision: 1.1 $
 66  
  * @since 1.0
 67  
  */
 68  
 public class UriTemplate implements Serializable
 69  
 {
 70  
 
 71  
 
 72  
     /**
 73  
      * The serialVersionUID
 74  
      */
 75  
     private static final long serialVersionUID = -5245084430838445979L;
 76  
 
 77  6
     public enum Encoding
 78  
     {
 79  2
         U, UR;
 80  
     }
 81  
 
 82  
     public static final String DEFAULT_SEPARATOR = ",";
 83  
 
 84  
     /**
 85  
      *
 86  
      */
 87  1010
     transient DateTimeFormatter defaultDateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
 88  
 
 89  
     /**
 90  
      * @deprecated Replaced by {@link #defaultDateTimeFormatter defaultDateTimeFormatter}
 91  
      */
 92  1010
     @Deprecated
 93  
     protected DateFormat defaultDateFormat = null;
 94  
 
 95  
     /**
 96  
      *
 97  
      */
 98  2
     private static final char[] OPERATORS =
 99  
     {'+', '#', '.', '/', ';', '?', '&', '!', '='};
 100  
 
 101  
     /**
 102  
      *
 103  
      */
 104  2
     private static final BitSet OPERATOR_BITSET = new BitSet();
 105  
 
 106  
     static
 107  
     {
 108  20
         for (int i = 0; i < OPERATORS.length; i++)
 109  
         {
 110  18
             OPERATOR_BITSET.set(OPERATORS[i]);
 111  
         }
 112  2
     }
 113  
 
 114  
     /**
 115  
      * The URI template String
 116  
      */
 117  
     private String template;
 118  
 
 119  
     /**
 120  
      * A regex string that matches the a URI to the template pattern
 121  
      */
 122  
     private Pattern reverseMatchPattern;
 123  
 
 124  
     /**
 125  
      * The collection of values that will be applied to the URI expression in the
 126  
      * expansion process.
 127  
      */
 128  1010
     private Map<String, Object> values = new LinkedHashMap<String, Object>();
 129  
 
 130  
     /**
 131  
      *
 132  
      */
 133  
     private LinkedList<UriTemplateComponent> components;
 134  
 
 135  
     /**
 136  
      *
 137  
      */
 138  
     private Expression[] expressions;
 139  
 
 140  
     /**
 141  
      *
 142  
      */
 143  
     private String[] variables;
 144  
 
 145  
     /**
 146  
      * Create a new UriTemplate.
 147  
      *
 148  
      * @param template
 149  
      * @throws MalformedUriTemplateException
 150  
      */
 151  
     private UriTemplate(final String template) throws MalformedUriTemplateException
 152  766
     {
 153  766
         this.template = template;
 154  766
         this.parseTemplateString();
 155  710
     }
 156  
 
 157  
     /**
 158  
      * Create a new UriTemplate.
 159  
      *
 160  
      * @param components
 161  
      */
 162  
     protected UriTemplate(LinkedList<UriTemplateComponent> components)
 163  244
     {
 164  244
         this.components = components;
 165  244
         initExpressions();
 166  244
         buildTemplateStringFromComponents();
 167  244
     }
 168  
 
 169  
     /**
 170  
      * Creates a new {@link UriTemplateBuilder} instance.
 171  
      * @return the new UriTemplateBuilder
 172  
      * @since 2.1.2
 173  
      */
 174  
     public static UriTemplateBuilder createBuilder()
 175  
     {
 176  180
         return new UriTemplateBuilder();
 177  
     }
 178  
 
 179  
     /**
 180  
      * Creates a new {@link UriTemplateBuilder} from the template string.
 181  
      *
 182  
      * @param template
 183  
      * @return
 184  
      * @throws MalformedUriTemplateException
 185  
      * @since 2.0
 186  
      */
 187  
     public static UriTemplateBuilder buildFromTemplate(String template) throws MalformedUriTemplateException
 188  
     {
 189  70
         return new UriTemplateBuilder(template);
 190  
     }
 191  
 
 192  
     /**
 193  
      * <p>
 194  
      * Creates a new {@link UriTemplateBuilder} from a root {@link UriTemplate}. This
 195  
      * method will create a new {@link UriTemplate} from the base and copy the variables
 196  
      * from the base template to the new {@link UriTemplate}.
 197  
      * </p>
 198  
      * <p>
 199  
      * This method is useful when the base template is less volatile than the child
 200  
      * expression and you want to merge the two.
 201  
      * </p>
 202  
      *
 203  
      * @param baseTemplate
 204  
      * @return
 205  
      * @since 2.0
 206  
      */
 207  
     public static UriTemplateBuilder buildFromTemplate(UriTemplate baseTemplate) throws MalformedUriTemplateException
 208  
     {
 209  182
         return new UriTemplateBuilder(baseTemplate);
 210  
     }
 211  
 
 212  
     /**
 213  
      * Creates a new {@link UriTemplate} from the template.
 214  
      *
 215  
      * @param templateString
 216  
      * @return
 217  
      * @since 2.0
 218  
      */
 219  
     public static final UriTemplate fromTemplate(final String templateString) throws MalformedUriTemplateException
 220  
     {
 221  742
         return new UriTemplate(templateString);
 222  
     }
 223  
 
 224  
     /**
 225  
      * <p>
 226  
      * Creates a new {@link UriTemplate} from a root {@link UriTemplate}. This
 227  
      * method will create a new {@link UriTemplate} from the base and copy the variables
 228  
      * from the base template to the new {@link UriTemplate}.
 229  
      * </p>
 230  
      * <p>
 231  
      * This method is useful when the base template is less volatile than the child
 232  
      * expression and you want to merge the two.
 233  
      * </p>
 234  
      *
 235  
      * @param baseTemplate
 236  
      * @return
 237  
      * @since 1.0
 238  
      */
 239  
     public static UriTemplateBuilder fromTemplate(UriTemplate baseTemplate) throws MalformedUriTemplateException
 240  
     {
 241  0
         return new UriTemplateBuilder(baseTemplate.getTemplate());
 242  
     }
 243  
 
 244  
     /**
 245  
      * Returns the collection of {@link UriTemplateComponent} instances
 246  
      * found in this template.
 247  
      *
 248  
      * @return
 249  
      */
 250  
     public Collection<UriTemplateComponent> getComponents() {
 251  2
         return Collections.unmodifiableCollection(components);
 252  
     }
 253  
 
 254  
     /**
 255  
      * Returns the number of expressions found in this template
 256  
      *
 257  
      * @return
 258  
      */
 259  
     public int expressionCount()
 260  
     {
 261  0
         return expressions.length;
 262  
     }
 263  
 
 264  
     /**
 265  
      * Returns an array of {@link Expression} instances found in this
 266  
      * template.
 267  
      *
 268  
      * @return
 269  
      */
 270  
     public Expression[] getExpressions()
 271  
     {
 272  0
         return expressions;
 273  
     }
 274  
 
 275  
     /**
 276  
      * Returns the list of unique variable names, from all {@link Expression}'s, in this template.
 277  
      *
 278  
      * @return
 279  
      */
 280  
     public String[] getVariables()
 281  
     {
 282  0
         if (variables == null)
 283  
         {
 284  0
             Set<String> vars = new LinkedHashSet<String>();
 285  0
             for (Expression e : getExpressions())
 286  
             {
 287  0
                 for (VarSpec v : e.getVarSpecs())
 288  
                 {
 289  0
                     vars.add(v.getVariableName());
 290  0
                 }
 291  
             }
 292  0
             variables = vars.toArray(new String[vars.size()]);
 293  
         }
 294  0
         return variables;
 295  
     }
 296  
 
 297  
     /**
 298  
      * Parse the URI template string into the template model.
 299  
      */
 300  
     protected void parseTemplateString() throws MalformedUriTemplateException
 301  
     {
 302  766
         final String templateString = getTemplate();
 303  766
         final UriTemplateParser scanner = new UriTemplateParser();
 304  766
         this.components = scanner.scan(templateString);
 305  710
         initExpressions();
 306  710
     }
 307  
 
 308  
     /**
 309  
      * Initializes the collection of expressions in the template.
 310  
      */
 311  
     private void initExpressions()
 312  
     {
 313  954
         final List<Expression> expressionList = new LinkedList<Expression>();
 314  954
         for (UriTemplateComponent c : components)
 315  
         {
 316  1454
             if (c instanceof Expression)
 317  
             {
 318  1066
                 expressionList.add((Expression) c);
 319  
             }
 320  
 
 321  1454
         }
 322  954
         expressions = expressionList.toArray(new Expression[expressionList.size()]);
 323  954
     }
 324  
 
 325  
 
 326  
     private void buildTemplateStringFromComponents()
 327  
     {
 328  244
         StringBuilder b = new StringBuilder();
 329  244
         for (UriTemplateComponent c : components)
 330  
         {
 331  406
             b.append(c.getValue());
 332  406
         }
 333  244
         this.template = b.toString();
 334  244
     }
 335  
 
 336  
     private void buildReverseMatchRegexFromComponents()
 337  
     {
 338  0
         StringBuilder b = new StringBuilder();
 339  0
         for (UriTemplateComponent c : components)
 340  
         {
 341  0
             b.append("(").append(c.getMatchPattern()).append(")");
 342  0
         }
 343  0
         this.reverseMatchPattern = Pattern.compile(b.toString());
 344  0
     }
 345  
 
 346  
     /**
 347  
      * Returns the
 348  
      *
 349  
      * @return
 350  
      */
 351  
     protected Pattern getReverseMatchPattern()
 352  
     {
 353  0
         if (this.reverseMatchPattern == null)
 354  
         {
 355  0
             buildReverseMatchRegexFromComponents();
 356  
         }
 357  0
         return this.reverseMatchPattern;
 358  
     }
 359  
 
 360  
     /**
 361  
      * Expands the given template string using the variable replacements
 362  
      * in the supplied {@link Map}.
 363  
      *
 364  
      * @param templateString
 365  
      * @param values
 366  
      * @return the expanded URI as a String
 367  
      * @throws MalformedUriTemplateException
 368  
      * @throws VariableExpansionException
 369  
      * @since 1.0
 370  
      */
 371  
     public static String expand(final String templateString, Map<String, Object> values)
 372  
     throws MalformedUriTemplateException, VariableExpansionException
 373  
     {
 374  8
         UriTemplate t = new UriTemplate(templateString);
 375  8
         t.set(values);
 376  8
         return t.expand();
 377  
     }
 378  
 
 379  
     /**
 380  
      * Expands the given template string using the variable replacements
 381  
      * in the supplied {@link Map}. Expressions without replacements get
 382  
      * preserved and still exist in the expanded URI string.
 383  
      *
 384  
      * @param templateString URI template
 385  
      * @param values         Replacements
 386  
      * @return The expanded URI as a String
 387  
      * @throws MalformedUriTemplateException
 388  
      * @throws VariableExpansionException
 389  
      */
 390  
     public static String expandPartial(final String templateString, Map<String, Object> values)
 391  
     throws MalformedUriTemplateException, VariableExpansionException
 392  
     {
 393  16
         UriTemplate t = new UriTemplate(templateString);
 394  16
         t.set(values);
 395  16
         return t.expandPartial();
 396  
     }
 397  
 
 398  
     /**
 399  
      * Expand the URI template using the supplied values
 400  
      *
 401  
      * @param vars The values that will be used in the expansion
 402  
      * @return the expanded URI as a String
 403  
      * @throw VariableExpansionException
 404  
      * @since 1.0
 405  
      */
 406  
     public String expand(Map<String, Object> vars) throws VariableExpansionException
 407  
     {
 408  418
         this.values = vars;
 409  418
         return expand();
 410  
     }
 411  
 
 412  
 
 413  
     /**
 414  
      * Applies variable substitution the URI Template and returns the expanded
 415  
      * URI.
 416  
      *
 417  
      * @return the expanded URI as a String
 418  
      * @throw VariableExpansionException
 419  
      * @since 1.0
 420  
      */
 421  
     public String expand() throws VariableExpansionException
 422  
     {
 423  520
         String template = getTemplate();
 424  1058
         for (Expression expression : expressions)
 425  
         {
 426  548
             final String replacement = expressionReplacementString(expression, false);
 427  538
             template = template.replaceAll(expression.getReplacementPattern(), replacement);
 428  
         }
 429  510
         return template;
 430  
     }
 431  
 
 432  
     /**
 433  
      * @return
 434  
      * @throws VariableExpansionException
 435  
      */
 436  
     public String expandPartial() throws VariableExpansionException
 437  
     {
 438  16
         String template = getTemplate();
 439  70
         for (Expression expression : expressions)
 440  
         {
 441  54
             final String replacement = expressionReplacementString(expression, true);
 442  54
             template = template.replaceAll(expression.getReplacementPattern(), replacement);
 443  
         }
 444  16
         return template;
 445  
     }
 446  
 
 447  
     /**
 448  
      * Returns the original URI template expression.
 449  
      *
 450  
      * @return the template string
 451  
      * @since 1.1.4
 452  
      */
 453  
     public String getTemplate()
 454  
     {
 455  1916
         return template;
 456  
     }
 457  
 
 458  
     /**
 459  
      * Returns the collection of name/value pairs contained in the instance.
 460  
      *
 461  
      * @return the name value pairs
 462  
      * @since 1.0
 463  
      */
 464  
     public Map<String, Object> getValues()
 465  
     {
 466  192
         return this.values;
 467  
     }
 468  
 
 469  
     /**
 470  
      * @param dateFormatString
 471  
      * @return the date format used to render dates
 472  
      * @since 1.0
 473  
      */
 474  
     public UriTemplate withDefaultDateFormat(String dateFormatString)
 475  
     {
 476  4
         return this.withDefaultDateFormat(DateTimeFormat.forPattern(dateFormatString));
 477  
     }
 478  
 
 479  
     private UriTemplate withDefaultDateFormat(DateTimeFormatter dateTimeFormatter)
 480  
     {
 481  4
         defaultDateTimeFormatter = dateTimeFormatter;
 482  4
         return this;
 483  
     }
 484  
 
 485  
     /**
 486  
      * @param dateFormat
 487  
      * @return the date format used to render dates
 488  
      * @since 1.0
 489  
      * @deprecated replaced by {@link #withDefaultDateFormat(String) withDefaultDateFormat}
 490  
      */
 491  
     @Deprecated
 492  
     public UriTemplate withDefaultDateFormat(DateFormat dateFormat)
 493  
     {
 494  2
         if (!(dateFormat instanceof SimpleDateFormat))
 495  
         {
 496  0
             throw new IllegalArgumentException(
 497  
             "The only supported subclass of java.text.DateFormat is java.text.SimpleDateFormat");
 498  
         }
 499  2
         defaultDateTimeFormatter = DateTimeFormat.forPattern(((SimpleDateFormat) dateFormat).toPattern());
 500  2
         return this;
 501  
     }
 502  
 
 503  
     /**
 504  
      * Sets a value on the URI template expression variable.
 505  
      *
 506  
      * @param variableName
 507  
      * @param value
 508  
      * @return
 509  
      * @since 1.0
 510  
      */
 511  
     public UriTemplate set(String variableName, Object value)
 512  
     {
 513  98
         values.put(variableName, value);
 514  98
         return this;
 515  
     }
 516  
 
 517  
     /**
 518  
      * Returns true if the {@link UriTemplate} contains the variableName.
 519  
      *
 520  
      * @param variableName
 521  
      * @return
 522  
      */
 523  
     public boolean hasVariable(String variableName)
 524  
     {
 525  0
         return values.containsKey(variableName);
 526  
     }
 527  
 
 528  
     /**
 529  
      * FIXME Comment this
 530  
      *
 531  
      * @param variableName
 532  
      * @return
 533  
      */
 534  
     public Object get(String variableName)
 535  
     {
 536  0
         return values.get(variableName);
 537  
     }
 538  
 
 539  
     /**
 540  
      * Sets a Date value into the list of variable substitutions using the
 541  
      * default {@link DateFormat}.
 542  
      *
 543  
      * @param variableName
 544  
      * @param value
 545  
      * @return
 546  
      * @since 1.0
 547  
      */
 548  
     public UriTemplate set(String variableName, Date value)
 549  
     {
 550  12
         values.put(variableName, value);
 551  12
         return this;
 552  
     }
 553  
 
 554  
     /**
 555  
      * Adds the name/value pairs in the supplied {@link Map} to the collection
 556  
      * of values within this URI template instance.
 557  
      *
 558  
      * @param values
 559  
      * @return
 560  
      * @since 1.0
 561  
      */
 562  
     public UriTemplate set(Map<String, Object> values)
 563  
     {
 564  28
         if (values != null && !values.isEmpty())
 565  
         {
 566  22
             this.values.putAll(values);
 567  
         }
 568  28
         return this;
 569  
     }
 570  
 
 571  
     /**
 572  
      * @param op
 573  
      * @return
 574  
      */
 575  
     public static boolean containsOperator(String op)
 576  
     {
 577  1052
         return OPERATOR_BITSET.get(op.toCharArray()[0]);
 578  
     }
 579  
 
 580  
 
 581  
     /**
 582  
      * @param expression
 583  
      * @param partial
 584  
      * @return
 585  
      * @throws VariableExpansionException
 586  
      */
 587  
     private String expressionReplacementString(Expression expression, boolean partial) throws VariableExpansionException
 588  
     {
 589  602
         final Operator operator = expression.getOperator();
 590  602
         final List<String> replacements = expandVariables(expression, partial);
 591  592
         String result = partial ? joinParts(expression, replacements) : joinParts(operator.getSeparator(), replacements);
 592  592
         if (result != null)
 593  
         {
 594  566
             if (!partial && operator != Operator.RESERVED)
 595  
             {
 596  452
                 result = operator.getPrefix() + result;
 597  
             }
 598  
         }
 599  
         else
 600  
         {
 601  26
             result = "";
 602  
         }
 603  592
         return result;
 604  
     }
 605  
 
 606  
     /**
 607  
      * @param expression
 608  
      * @param partial
 609  
      * @return
 610  
      * @throws VariableExpansionException
 611  
      */
 612  
     @SuppressWarnings({"rawtypes", "unchecked"})
 613  
     private List<String> expandVariables(Expression expression, boolean partial) throws VariableExpansionException
 614  
     {
 615  602
         final List<String> replacements = new ArrayList<String>();
 616  602
         final Operator operator = expression.getOperator();
 617  602
         for (VarSpec varSpec : expression.getVarSpecs())
 618  
         {
 619  822
             if (values.containsKey(varSpec.getVariableName()))
 620  
             {
 621  756
                 Object value = values.get(varSpec.getVariableName());
 622  
                 // The expanded value
 623  756
                 String expanded = null;
 624  
 
 625  756
                 if (value != null)
 626  
                 {
 627  734
                     if (value.getClass().isArray())
 628  
                     {
 629  28
                         if (value instanceof char[][])
 630  
                         {
 631  2
                             final char[][] chars = (char[][]) value;
 632  2
                             final List<String> strings = new ArrayList<String>();
 633  6
                             for (char[] c : chars)
 634  
                             {
 635  4
                                 strings.add(String.valueOf(c));
 636  
                             }
 637  2
                             value = strings;
 638  2
                         }
 639  26
                         else if (value instanceof char[])
 640  
                         {
 641  2
                             value = String.valueOf((char[]) value);
 642  
                         }
 643  
                         else
 644  
                         {
 645  24
                             value = arrayToList(value);
 646  
                         }
 647  
 
 648  
                     }
 649  
                 }
 650  
                 // Check to see if the value is explodable, meaning that we need to pass it to VarExploder to
 651  
                 // decompose the object to simple key/value pairs. We don't handle prefix modifiers on composite values.
 652  754
                 final boolean explodable = isExplodable(value);
 653  754
                 if (explodable && varSpec.getModifier() == Modifier.PREFIX)
 654  
                 {
 655  4
                     throw new VariableExpansionException(
 656  
                     "Prefix modifiers are not applicable to variables that have composite values.");
 657  
                 }
 658  
                 // If it's explodable, lookup the appropriate exploder
 659  750
                 if (explodable)
 660  
                 {
 661  
                     final VarExploder exploder;
 662  238
                     if (value instanceof VarExploder)
 663  
                     {
 664  6
                         exploder = (VarExploder) value;
 665  
                     }
 666  
                     else
 667  
                     {
 668  232
                         exploder = VarExploderFactory.getExploder(value, varSpec);
 669  
                     }
 670  238
                     if (varSpec.getModifier() == Modifier.EXPLODE)
 671  
                     {
 672  132
                         expanded = expandMap(operator, varSpec, exploder.getNameValuePairs());
 673  
                     }
 674  106
                     else if (varSpec.getModifier() != Modifier.EXPLODE)
 675  
                     {
 676  106
                         expanded = expandCollection(operator, varSpec, exploder.getValues());
 677  
                     }
 678  
                 }
 679  
 
 680  
                 /*
 681  
                  * Format the date if we have a java.util.Date
 682  
                  */
 683  746
                 if (value instanceof Date)
 684  
                 {
 685  16
                     value = defaultDateTimeFormatter.print(new DateTime((Date) value));
 686  
                 }
 687  
                 /*
 688  
                  * The variable value contains a list of values
 689  
                  */
 690  746
                 if (value instanceof Collection)
 691  
                 {
 692  142
                     expanded = this.expandCollection(operator, varSpec, (Collection) value);
 693  
                 }
 694  
                 /*
 695  
                  * The variable value contains a list of key-value pairs
 696  
                  */
 697  604
                 else if (value instanceof Map)
 698  
                 {
 699  72
                     expanded = expandMap(operator, varSpec, (Map) value);
 700  
                 }
 701  
                 /*
 702  
                  * The variable value is null or has o value.
 703  
                  */
 704  532
                 else if (value == null)
 705  
                 {
 706  22
                     expanded = null;
 707  
                 }
 708  
                 /*
 709  
                  * the value hasn't been expanded yet and we should call toString() on it.
 710  
                  */
 711  510
                 else if (expanded == null)
 712  
                 {
 713  490
                     expanded = this.expandStringValue(operator, varSpec, value.toString(), VarSpec.VarFormat.SINGLE);
 714  
                 }
 715  746
                 if (expanded != null)
 716  
                 {
 717  706
                     replacements.add(expanded);
 718  
                 }
 719  
 
 720  746
             }
 721  66
             else if (partial)
 722  
             {
 723  58
                 replacements.add(null);
 724  
             }
 725  812
         }
 726  592
         return replacements;
 727  
     }
 728  
 
 729  
     /**
 730  
      * @param value
 731  
      * @return
 732  
      */
 733  
     private boolean isExplodable(Object value)
 734  
     {
 735  754
         if (value == null)
 736  
         {
 737  22
             return false;
 738  
         }
 739  732
         if (value instanceof Collection || value instanceof Map || value.getClass().isArray())
 740  
         {
 741  218
             return true;
 742  
         }
 743  514
         if (!isSimpleType(value))
 744  
         {
 745  24
             return true;
 746  
         }
 747  490
         return false;
 748  
     }
 749  
 
 750  
     /**
 751  
      * Returns true of the object is:
 752  
      * <p>
 753  
      * <ul>
 754  
      * <li>a primitive type</li>
 755  
      * <li>an enum</li>
 756  
      * <li>an instance of {@link CharSequence}</li>
 757  
      * <li>an instance of {@link Number} <li>
 758  
      * <li>an instance of {@link Date} <li>
 759  
      * <li>an instance of {@link Boolean}</li>
 760  
      * <li>an instance of {@link UUID}</li>
 761  
      * <li>an instance of {@link Class}</li>
 762  
      * </ul>
 763  
      *
 764  
      * @param value
 765  
      * @return
 766  
      */
 767  
     private boolean isSimpleType(Object value)
 768  
     {
 769  
 
 770  1500
         if (value.getClass().isPrimitive()
 771  1500
         || value.getClass().isEnum()
 772  
         || value instanceof Class
 773  
         || value instanceof Number
 774  
         || value instanceof CharSequence
 775  
         || value instanceof Date
 776  
         || value instanceof Boolean
 777  
         || value instanceof UUID
 778  
         )
 779  
         {
 780  1474
             return true;
 781  
         }
 782  26
         return false;
 783  
     }
 784  
 
 785  
     /**
 786  
      * @param operator
 787  
      * @param varSpec
 788  
      * @param variable
 789  
      * @return
 790  
      */
 791  
     private String expandCollection(Operator operator, VarSpec varSpec, Collection<?> variable)
 792  
     throws VariableExpansionException
 793  
     {
 794  
 
 795  248
         if (variable == null || variable.isEmpty())
 796  
         {
 797  14
             return null;
 798  
         }
 799  
 
 800  234
         final List<String> stringValues = new ArrayList<String>();
 801  234
         final Iterator<?> i = variable.iterator();
 802  234
         String separator = operator.getSeparator();
 803  234
         if (varSpec.getModifier() != Modifier.EXPLODE)
 804  
         {
 805  164
             separator = operator.getListSeparator();
 806  
         }
 807  808
         while (i.hasNext())
 808  
         {
 809  574
             final Object obj = i.next();
 810  574
             checkValue(obj);
 811  
             String value;
 812  574
             if(checkValue(obj))
 813  
             {
 814  8
                 value = joinParts(",", obj);
 815  
             }
 816  
             else
 817  
             {
 818  566
                 if(isSimpleType(obj))
 819  
                 {
 820  566
                     value = obj.toString();
 821  
                 }
 822  
                 else
 823  
                 {
 824  0
                     throw new VariableExpansionException("Collections or other complex types are not supported in collections.");
 825  
                 }
 826  
             }
 827  
 
 828  574
             stringValues.add(expandStringValue(operator, varSpec, value, VarSpec.VarFormat.ARRAY));
 829  574
         }
 830  
 
 831  234
         if (varSpec.getModifier() != Modifier.EXPLODE && operator.useVarNameWhenExploded())
 832  
         {
 833  64
             final String parts = joinParts(separator, stringValues);
 834  64
             if (operator == Operator.QUERY && parts == null)
 835  
             {
 836  0
                 return varSpec.getVariableName() + "=";
 837  
             }
 838  64
             return varSpec.getVariableName() + "=" + parts;
 839  
         }
 840  170
         return joinParts(separator, stringValues);
 841  
     }
 842  
 
 843  
     /**
 844  
      * Check to ensure that the values being passed down do not contain nested data structures.
 845  
      *
 846  
      * @param obj
 847  
      */
 848  
     private boolean checkValue(Object obj) throws VariableExpansionException
 849  
     {
 850  1586
         if (obj instanceof Map)
 851  
         {
 852  2
             throw new VariableExpansionException("Nested data structures are not supported.");
 853  
         }
 854  
 
 855  1584
         if (obj instanceof Collection || obj.getClass().isArray())
 856  
         {
 857  32
             return true;
 858  
         }
 859  1552
         return false;
 860  
     }
 861  
 
 862  
     /**
 863  
      * @param operator
 864  
      * @param varSpec
 865  
      * @param variable
 866  
      * @return
 867  
      */
 868  
     private String expandMap(Operator operator, VarSpec varSpec, Map<String, Object> variable)
 869  
     throws VariableExpansionException
 870  
     {
 871  204
         if (variable == null || variable.isEmpty())
 872  
         {
 873  10
             return null;
 874  
         }
 875  
 
 876  194
         List<String> stringValues = new ArrayList<String>();
 877  194
         String pairJoiner = "=";
 878  194
         if (varSpec.getModifier() != Modifier.EXPLODE)
 879  
         {
 880  32
             pairJoiner = ",";
 881  
         }
 882  194
         String joiner = operator.getSeparator();
 883  194
         if (varSpec.getModifier() != Modifier.EXPLODE)
 884  
         {
 885  32
             joiner = operator.getListSeparator();
 886  
         }
 887  194
         for (Entry<String, Object> entry : variable.entrySet())
 888  
         {
 889  
 
 890  438
             String key = entry.getKey();
 891  
             String value;
 892  438
             if(checkValue(entry.getValue()))
 893  
             {
 894  16
                 value = joinParts(",", entry.getValue());
 895  
             }
 896  
             else
 897  
             {
 898  420
                 if(isSimpleType(entry.getValue()))
 899  
                 {
 900  418
                     value = entry.getValue().toString();
 901  
                 }
 902  
                 else
 903  
                 {
 904  2
                     throw new VariableExpansionException("Collections or other complex types are not supported in collections.");
 905  
                 }
 906  
             }
 907  
 
 908  434
             String pair = expandStringValue(operator, varSpec, key, VarSpec.VarFormat.PAIRS) + pairJoiner
 909  434
             + expandStringValue(operator, varSpec, value, VarSpec.VarFormat.PAIRS);
 910  
 
 911  434
             stringValues.add(pair);
 912  434
         }
 913  
 
 914  190
         if (varSpec.getModifier() != Modifier.EXPLODE
 915  
         && (operator == Operator.MATRIX || operator == Operator.QUERY || operator == Operator.CONTINUATION))
 916  
         {
 917  12
             String joinedValues = joinParts(joiner, stringValues);
 918  12
             if (operator == Operator.QUERY && joinedValues == null)
 919  
             {
 920  0
                 return varSpec.getVariableName() + "=";
 921  
             }
 922  12
             return varSpec.getVariableName() + "=" + joinedValues;
 923  
         }
 924  
 
 925  178
         return joinParts(joiner, stringValues);
 926  
     }
 927  
 
 928  
     /**
 929  
      * This method performs the expansion on the string value being applied to the output URI. The rules for exapnasion
 930  
      * depends heavily on the {@link Operator} in use. The {@link Operator} will dictate the URI encoding rules that
 931  
      * will be applied to the string.
 932  
      *
 933  
      *
 934  
      * @param operator
 935  
      * @param varSpec
 936  
      * @param variable
 937  
      * @param format
 938  
      * @return
 939  
      */
 940  
     private String expandStringValue(Operator operator, VarSpec varSpec, String variable, VarSpec.VarFormat format)
 941  
     throws VariableExpansionException
 942  
     {
 943  
         String expanded;
 944  
 
 945  1932
         if (varSpec.getModifier() == Modifier.PREFIX)
 946  
         {
 947  48
             int position = varSpec.getPosition();
 948  48
             if (position < variable.length())
 949  
             {
 950  44
                 variable = variable.substring(0, position);
 951  
             }
 952  
         }
 953  
 
 954  
         try
 955  
         {
 956  
             // If we have a {+} or {#} operator, there are items we do not need to encode.
 957  1932
             if (operator.getEncoding() == Encoding.UR)
 958  
             {
 959  366
                 expanded = UriUtil.encodeFragment(variable);
 960  
             }
 961  
             else
 962  
             {
 963  1566
                 expanded = UriUtil.encode(variable);
 964  
             }
 965  
         }
 966  0
         catch (UnsupportedEncodingException e)
 967  
         {
 968  0
             throw new VariableExpansionException("Could not expand variable due to a problem URI encoding the value.", e);
 969  1932
         }
 970  
 
 971  1932
         if (operator.isNamed())
 972  
         {
 973  824
             if (expanded.isEmpty() && !"&".equals(operator.getSeparator()) )
 974  
             {
 975  8
                 expanded = varSpec.getValue();
 976  
             }
 977  816
             else if (format == VarSpec.VarFormat.SINGLE)
 978  
             {
 979  170
                 expanded = varSpec.getVariableName() + "=" + expanded;
 980  
             }
 981  
 
 982  
             else
 983  
             {
 984  646
                 if (varSpec.getModifier() == Modifier.EXPLODE &&
 985  424
                     operator.useVarNameWhenExploded() &&
 986  
                     format != VarSpec.VarFormat.PAIRS)
 987  
                     {
 988  64
                         expanded = varSpec.getVariableName() + "=" + expanded;
 989  
                     }
 990  
 
 991  
             }
 992  
         }
 993  1932
         return expanded;
 994  
     }
 995  
 
 996  
 
 997  
     private String joinParts(final String joiner, Object parts)
 998  
     {
 999  24
         if(parts instanceof Collection)
 1000  
         {
 1001  24
             Collection<String> values = (Collection)parts;
 1002  24
             List<String> v = new ArrayList<String>();
 1003  24
             for(String s : values)
 1004  
             {
 1005  60
                 v.add(s);
 1006  60
             }
 1007  24
             return joinParts(joiner,v);
 1008  
         }
 1009  0
         else if (parts.getClass().isArray())
 1010  
         {
 1011  0
             List<String> v = Arrays.asList((String[]) parts);
 1012  0
             return joinParts(joiner,v);
 1013  
         }
 1014  0
         return null;
 1015  
 
 1016  
     }
 1017  
     /**
 1018  
      * @param joiner
 1019  
      * @param parts
 1020  
      * @return
 1021  
      */
 1022  
     private String joinParts(final String joiner, List<String> parts)
 1023  
     {
 1024  1040
         if (parts.isEmpty())
 1025  
         {
 1026  26
             return null;
 1027  
         }
 1028  
 
 1029  1014
         if (parts.size() == 1)
 1030  
         {
 1031  566
             return parts.get(0);
 1032  
         }
 1033  
 
 1034  448
         final StringBuilder builder = new StringBuilder();
 1035  1694
         for (int i = 0; i < parts.size(); i++)
 1036  
         {
 1037  1246
             final String part = parts.get(i);
 1038  1246
             if (!part.isEmpty())
 1039  
             {
 1040  1240
                 builder.append(part);
 1041  1240
                 if (parts.size() > 0 && i != (parts.size() - 1))
 1042  
                 {
 1043  798
                     builder.append(joiner);
 1044  
                 }
 1045  
             }
 1046  
 
 1047  
         }
 1048  448
         return builder.toString();
 1049  
     }
 1050  
 
 1051  
    /**
 1052  
     * Joins parts by preserving expressions without values.
 1053  
     * @param expression Expression for the given parts
 1054  
     * @param parts Parts to join
 1055  
     * @return Joined parts
 1056  
     */
 1057  
    private String joinParts(final Expression expression, List<String> parts)
 1058  
    {
 1059  
 
 1060  54
       int[] index = getIndexForPartsWithNullsFirstIfQueryOrRegularSequnceIfNot(expression, parts);
 1061  
 
 1062  54
       List<String> replacedParts = new ArrayList<String>(parts.size());
 1063  144
       for(int i = 0; i < parts.size(); i++) {
 1064  90
          StringBuilder builder = new StringBuilder();
 1065  90
          if(parts.get(index[i]) == null)
 1066  
          {
 1067  40
             builder.append('{');
 1068  98
             while(i < parts.size() && parts.get(index[i]) == null)
 1069  
             {
 1070  58
                if(builder.length() == 1)
 1071  
                {
 1072  40
                   builder.append(replacedParts.size() == 0 ? expression.getOperator().getPrefix() : expression.getOperator().getSeparator());
 1073  
                }
 1074  
                else
 1075  
                {
 1076  18
                   builder.append(DEFAULT_SEPARATOR);
 1077  
                }
 1078  58
                builder.append(expression.getVarSpecs().get(index[i]).getValue());
 1079  58
                i++;
 1080  
             }
 1081  40
             i--;
 1082  40
             builder.append('}');
 1083  
          } else {
 1084  50
            if(expression.getOperator() != Operator.RESERVED) {
 1085  46
             builder.append(replacedParts.size() == 0 ? expression.getOperator().getPrefix() : expression.getOperator().getSeparator());
 1086  
            }
 1087  50
            builder.append(parts.get(index[i]));
 1088  
          }
 1089  90
          replacedParts.add(builder.toString());
 1090  
       }
 1091  54
       return joinParts("", replacedParts);
 1092  
    }
 1093  
 
 1094  
    /**
 1095  
     * Takes an array of objects and converts them to a {@link List}.
 1096  
     *
 1097  
     * @param array
 1098  
     * @return
 1099  
     */
 1100  
    private List<Object> arrayToList(Object array) throws VariableExpansionException
 1101  
    {
 1102  24
       List<Object> list = new ArrayList<Object>();
 1103  24
       int length = Array.getLength(array);
 1104  90
       for (int i = 0; i < length; i++)
 1105  
       {
 1106  68
          final Object element = Array.get(array, i);
 1107  68
          if (element.getClass().isArray())
 1108  
          {
 1109  2
             throw new VariableExpansionException("Multi-dimenesional arrays are not supported.");
 1110  
          }
 1111  66
          list.add(element);
 1112  
       }
 1113  22
       return list;
 1114  
    }
 1115  
 
 1116  
    /**
 1117  
     * Takes the expression and the parts and generate a index with null value parts pulled to the start and
 1118  
     * the not null value parts pushed to the end. Ex:
 1119  
     * ["var3",null,"var1",null] will generate the following index:
 1120  
     * [1,3,0,2]
 1121  
     * @param expression
 1122  
     * @param parts
 1123  
     * @return
 1124  
     */
 1125  
    private int[] getIndexForPartsWithNullsFirstIfQueryOrRegularSequnceIfNot(final Expression expression, List<String> parts)
 1126  
    {
 1127  54
       int[] index = new int[parts.size()];
 1128  
 
 1129  54
       int inverse, forward = 0, backward = parts.size() - 1;
 1130  162
       for (int i = 0; i < parts.size(); i++) {
 1131  108
          if (expression.getOperator() == Operator.QUERY)
 1132  
          {
 1133  28
             inverse = parts.size() - i - 1;
 1134  28
             if (parts.get(i) != null)
 1135  
             {
 1136  10
                index[forward++] = i;
 1137  
             }
 1138  28
             if (parts.get(inverse) == null)
 1139  
             {
 1140  18
                index[backward--] = inverse;
 1141  
             }
 1142  
          }
 1143  
          else
 1144  
          {
 1145  80
             index[i] = i;
 1146  
          }
 1147  
       }
 1148  54
       return index;
 1149  
    }
 1150  
 
 1151  
 
 1152  
 
 1153  
 
 1154  
    /**
 1155  
     *
 1156  
     *
 1157  
     * @return
 1158  
     */
 1159  
 //   public String getRegexString()
 1160  
 //   {
 1161  
 //      return null;
 1162  
 //   }
 1163  
 }