001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest;
014
015import static org.apache.juneau.internal.ArrayUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021import javax.servlet.http.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.httppart.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.json.*;
027import org.apache.juneau.parser.*;
028import org.apache.juneau.utils.*;
029
030/**
031 * Represents the query parameters in an HTTP request.
032 * 
033 * <p>
034 * Similar in functionality to the {@link HttpServletRequest#getParameter(String)} except only looks in the URL string, not parameters from
035 * URL-Encoded FORM posts.
036 * <br>This can be useful in cases where you're using GET parameters on FORM POSTs, and you don't want the body of the request to be read.
037 * 
038 * <h5 class='section'>See Also:</h5>
039 * <ul>
040 *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.RequestQuery">Overview &gt; juneau-rest-server &gt; RequestQuery</a>
041 * </ul>
042 */
043@SuppressWarnings("unchecked")
044public final class RequestQuery extends LinkedHashMap<String,String[]> {
045   private static final long serialVersionUID = 1L;
046
047   private HttpPartParser parser;
048   private BeanSession beanSession;
049
050   RequestQuery parser(HttpPartParser parser) {
051      this.parser = parser;
052      return this;
053   }
054
055   RequestQuery beanSession(BeanSession beanSession) {
056      this.beanSession = beanSession;
057      return this;
058   }
059
060   /*
061    * Create a copy of the request query parameters.
062    */
063   RequestQuery copy() {
064      RequestQuery rq = new RequestQuery();
065      rq.putAll(this);
066      return rq;
067   }
068
069   /**
070    * Adds default entries to these query parameters.
071    * 
072    * <p>
073    * This includes the default queries defined at the resource and method levels.
074    * 
075    * @param defaultEntries 
076    *    The default entries.  
077    *    <br>Can be <jk>null</jk>.
078    * @return This object (for method chaining).
079    */
080   public RequestQuery addDefault(Map<String,Object> defaultEntries) {
081      if (defaultEntries != null) {
082         for (Map.Entry<String,Object> e : defaultEntries.entrySet()) {
083            String key = e.getKey();
084            Object value = e.getValue();
085            String[] v = get(key);
086            if (v == null || v.length == 0 || StringUtils.isEmpty(v[0]))
087               put(key, asStrings(value));
088         }
089      }
090      return this;
091   }
092
093   /**
094    * Adds a default entries to these query parameters.
095    * 
096    * <p>
097    * Similar to {@link #put(String, Object)} but doesn't override existing values.
098    * 
099    * @param name 
100    *    The query parameter name.  
101    * @param value
102    *    The query parameter value.  
103    *    <br>Converted to a String using <code>toString()</code>.
104    *    <br>Ignored if value is <jk>null</jk> or blank.
105    * @return This object (for method chaining).
106    */
107   public RequestQuery addDefault(String name, Object value) {
108      return addDefault(Collections.singletonMap(name, value));
109   }
110
111   /**
112    * Sets a request query parameter value.
113    * 
114    * <p>
115    * This overwrites any existing value.
116    * 
117    * @param name The parameter name.
118    * @param value 
119    *    The parameter value.
120    *    <br>Can be <jk>null</jk>.
121    */
122   public void put(String name, Object value) {
123      if (value == null)
124         put(name, null);
125      else
126         put(name, asStrings(value));
127   }
128
129   /**
130    * Returns a query parameter value as a string.
131    * 
132    * <p>
133    * If multiple query parameters have the same name, this returns only the first instance.
134    * 
135    * @param name The URL parameter name.
136    * @return 
137    *    The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
138    */
139   public String getString(String name) {
140      String[] v = get(name);
141      if (v == null || v.length == 0)
142         return null;
143
144      // Fix for behavior difference between Tomcat and WAS.
145      // getParameter("foo") on "&foo" in Tomcat returns "".
146      // getParameter("foo") on "&foo" in WAS returns null.
147      if (v.length == 1 && v[0] == null)
148         return "";
149
150      return v[0];
151   }
152
153   /**
154    * Same as {@link #getString(String)} but returns the specified default value if the query parameter was not
155    * specified.
156    * 
157    * @param name The URL parameter name.
158    * @param def The default value.
159    * @return
160    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
161    */
162   public String getString(String name, String def) {
163      String s = getString(name);
164      return StringUtils.isEmpty(s) ? def : s;
165   }
166
167   /**
168    * Same as {@link #getString(String)} but converts the value to an integer.
169    * 
170    * @param name The URL parameter name.
171    * @return
172    *    The parameter value, or <code>0</code> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
173    */
174   public int getInt(String name) {
175      return getInt(name, 0);
176   }
177
178   /**
179    * Same as {@link #getString(String,String)} but converts the value to an integer.
180    * 
181    * @param name The URL parameter name.
182    * @param def The default value.
183    * @return
184    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
185    */
186   public int getInt(String name, int def) {
187      String s = getString(name);
188      return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
189   }
190
191   /**
192    * Same as {@link #getString(String)} but converts the value to a boolean.
193    * 
194    * @param name The URL parameter name.
195    * @return
196    *    The parameter value, or <jk>false</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
197    */
198   public boolean getBoolean(String name) {
199      return getBoolean(name, false);
200   }
201
202   /**
203    * Same as {@link #getString(String,String)} but converts the value to a boolean.
204    * 
205    * @param name The URL parameter name.
206    * @param def The default value.
207    * @return
208    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
209    */
210   public boolean getBoolean(String name, boolean def) {
211      String s = getString(name);
212      return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
213   }
214
215   /**
216    * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource.
217    * 
218    * <h5 class='section'>Examples:</h5>
219    * <p class='bcode'>
220    *    <jc>// Parse into an integer.</jc>
221    *    <jk>int</jk> myparam = query.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>);
222    * 
223    *    <jc>// Parse into an int array.</jc>
224    *    <jk>int</jk>[] myparam = query.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>);
225
226    *    <jc>// Parse into a bean.</jc>
227    *    MyBean myparam = query.get(<js>"myparam"</js>, MyBean.<jk>class</jk>);
228    * 
229    *    <jc>// Parse into a linked-list of objects.</jc>
230    *    List myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>);
231    * 
232    *    <jc>// Parse into a map of object keys/values.</jc>
233    *    Map myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>);
234    * </p>
235    * 
236    * <h5 class='section'>See Also:</h5>
237    * <ul>
238    *    <li class='jf'>{@link RestContext#REST_partParser}
239    * </ul>
240    * 
241    * @param name The parameter name.
242    * @param type The class type to convert the parameter value to.
243    * @param <T> The class type to convert the parameter value to.
244    * @return The parameter value converted to the specified class type.
245    * @throws ParseException
246    */
247   public <T> T get(String name, Class<T> type) throws ParseException {
248      return get(null, name, type);
249   }
250
251   /**
252    * Same as {@link #get(String, Class)} but allows you to override the part parser.
253    * 
254    * @param parser
255    *    The parser to use for parsing the string value.
256    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 
257    * @param name The parameter name.
258    * @param type The class type to convert the parameter value to.
259    * @param <T> The class type to convert the parameter value to.
260    * @return The parameter value converted to the specified class type.
261    * @throws ParseException
262    */
263   public <T> T get(HttpPartParser parser, String name, Class<T> type) throws ParseException {
264      return get(parser, name, getClassMeta(type));
265   }
266
267   /**
268    * Same as {@link #get(String, Class)} except returns a default value if not found.
269    * 
270    * @param name The parameter name.
271    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
272    * @param type The class type to convert the parameter value to.
273    * @param <T> The class type to convert the parameter value to.
274    * @return The parameter value converted to the specified class type.
275    * @throws ParseException
276    */
277   public <T> T get(String name, T def, Class<T> type) throws ParseException {
278      return get(null, name, def, type);
279   }
280
281   /**
282    * Same as {@link #get(String, Object, Class)} but allows you to override the part parser.
283    * 
284    * @param parser
285    *    The parser to use for parsing the string value.
286    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 
287    * @param name The parameter name.
288    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
289    * @param type The class type to convert the parameter value to.
290    * @param <T> The class type to convert the parameter value to.
291    * @return The parameter value converted to the specified class type.
292    * @throws ParseException
293    */
294   public <T> T get(HttpPartParser parser, String name, T def, Class<T> type) throws ParseException {
295      return get(parser, name, def, getClassMeta(type));
296   }
297
298   /**
299    * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource.
300    * 
301    * <p>
302    * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created.
303    * 
304    * <h5 class='section'>Examples:</h5>
305    * <p class='bcode'>
306    *    <jc>// Parse into a linked-list of strings.</jc>
307    *    List&lt;String&gt; myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
308    * 
309    *    <jc>// Parse into a linked-list of linked-lists of strings.</jc>
310    *    List&lt;List&lt;String&gt;&gt; myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
311    * 
312    *    <jc>// Parse into a map of string keys/values.</jc>
313    *    Map&lt;String,String&gt; myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
314    * 
315    *    <jc>// Parse into a map containing string keys and values of lists containing beans.</jc>
316    *    Map&lt;String,List&lt;MyBean&gt;&gt; myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
317    * </p>
318    * 
319    * <h5 class='section'>Notes:</h5>
320    * <ul class='spaced-list'>
321    *    <li>
322    *       <code>Collections</code> must be followed by zero or one parameter representing the value type.
323    *    <li>
324    *       <code>Maps</code> must be followed by zero or two parameters representing the key and value types.
325    * </ul>
326    * 
327    * <h5 class='section'>See Also:</h5>
328    * <ul>
329    *    <li class='jf'>{@link RestContext#REST_partParser}
330    * </ul>
331    * 
332    * @param name The parameter name.
333    * @param type
334    *    The type of object to create.
335    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
336    * @param args
337    *    The type arguments of the class if it's a collection or map.
338    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
339    *    <br>Ignored if the main type is not a map or collection.
340    * @param <T> The class type to convert the parameter value to.
341    * @return The parameter value converted to the specified class type.
342    * @throws ParseException
343    */
344   public <T> T get(String name, Type type, Type...args) throws ParseException {
345      return get((HttpPartParser)null, name, type, args);
346   }
347
348   /**
349    * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser.
350    * 
351    * @param parser
352    *    The parser to use for parsing the string value.
353    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 
354    * 
355    * @param name The parameter name.
356    * @param type
357    *    The type of object to create.
358    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
359    * @param args
360    *    The type arguments of the class if it's a collection or map.
361    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
362    *    <br>Ignored if the main type is not a map or collection.
363    * @param <T> The class type to convert the parameter value to.
364    * @return The parameter value converted to the specified class type.
365    * @throws ParseException
366    */
367   public <T> T get(HttpPartParser parser, String name, Type type, Type...args) throws ParseException {
368      return (T)parse(parser, name, getClassMeta(type, args));
369   }
370
371   /**
372    * Same as {@link #get(String, Class)} except returns a default value if not found.
373    * 
374    * @param name The parameter name.
375    * @param type
376    *    The type of object to create.
377    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
378    * @param args
379    *    The type arguments of the class if it's a collection or map.
380    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
381    *    <br>Ignored if the main type is not a map or collection.
382    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
383    * @param <T> The class type to convert the parameter value to.
384    * @return The parameter value converted to the specified class type.
385    * @throws ParseException
386    */
387   public <T> T get(String name, Object def, Type type, Type...args) throws ParseException {
388      return get(null, name, def, type, args);
389   }
390
391   /**
392    * Same as {@link #get(String, Object, Type, Type...)} but allows you to override the part parser.
393    * 
394    * @param parser
395    *    The parser to use for parsing the string value.
396    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 
397    * @param name The parameter name.
398    * @param type
399    *    The type of object to create.
400    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
401    * @param args
402    *    The type arguments of the class if it's a collection or map.
403    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
404    *    <br>Ignored if the main type is not a map or collection.
405    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
406    * @param <T> The class type to convert the parameter value to.
407    * @return The parameter value converted to the specified class type.
408    * @throws ParseException
409    */
410   public <T> T get(HttpPartParser parser, String name, Object def, Type type, Type...args) throws ParseException {
411      return (T)parse(parser, name, def, getClassMeta(type, args));
412   }
413
414   /**
415    * Same as {@link #get(String, Class)} except for use on multi-part parameters
416    * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;key=@(1,2,3)"</js>).
417    * 
418    * <p>
419    * This method must only be called when parsing into classes of type Collection or array.
420    * 
421    * @param name The query parameter name.
422    * @param c The class type to convert the parameter value to.
423    * @param <T> The class type to convert the parameter value to.
424    * @return The query parameter value converted to the specified class type.
425    * @throws ParseException
426    */
427   public <T> T getAll(String name, Class<T> c) throws ParseException {
428      return getAll(name, beanSession.getClassMeta(c));
429   }
430
431   /**
432    * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters
433    * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;key=@(1,2,3)"</js>).
434    * 
435    * <p>
436    * This method must only be called when parsing into classes of type Collection or array.
437    * 
438    * @param name The query parameter name.
439    * @param type
440    *    The type of object to create.
441    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
442    * @param args
443    *    The type arguments of the class if it's a collection or map.
444    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
445    *    <br>Ignored if the main type is not a map or collection.
446    * @param <T> The class type to convert the parameter value to.
447    * @return The query parameter value converted to the specified class type.
448    * @throws ParseException
449    */
450   public <T> T getAll(String name, Type type, Type...args) throws ParseException {
451      return getAll(null, name, type, args);
452   }
453
454   /**
455    * Same as {@link #getAll(String, Type, Type...)} but allows you to override the part parser.
456    * 
457    * @param parser
458    *    The parser to use for parsing the string value.
459    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 
460    * @param name The query parameter name.
461    * @param type
462    *    The type of object to create.
463    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
464    * @param args
465    *    The type arguments of the class if it's a collection or map.
466    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
467    *    <br>Ignored if the main type is not a map or collection.
468    * @param <T> The class type to convert the parameter value to.
469    * @return The query parameter value converted to the specified class type.
470    * @throws ParseException
471    */
472   public <T> T getAll(HttpPartParser parser, String name, Type type, Type...args) throws ParseException {
473      return (T)parseAll(parser, name, getClassMeta(type, args));
474   }
475
476   /**
477    * Returns <jk>true</jk> if the request contains any of the specified query parameters.
478    * 
479    * @param params The list of parameters to check for.
480    * @return <jk>true</jk> if the request contains any of the specified query parameters.
481    */
482   public boolean containsAnyKeys(String...params) {
483      for (String p : params)
484         if (containsKey(p))
485            return true;
486      return false;
487   }
488
489   /**
490    * Locates the special search query arguments in the query and returns them as a {@link SearchArgs} object.
491    * 
492    * <p>
493    * The query arguments are as follows:
494    * <ul class='spaced-list'>
495    *    <li>
496    *       <js>"&amp;s="</js> - A comma-delimited list of column-name/search-token pairs.
497    *       <br>Example: <js>"&amp;s=column1=foo*,column2=*bar"</js>
498    *    <li>
499    *       <js>"&amp;v="</js> - A comma-delimited list column names to view.
500    *       <br>Example: <js>"&amp;v=column1,column2"</js>
501    *    <li>
502    *       <js>"&amp;o="</js> - A comma-delimited list column names to sort by.
503    *       <br>Column names can be suffixed with <js>'-'</js> to indicate descending order.
504    *       <br>Example: <js>"&amp;o=column1,column2-"</js>
505    *    <li>
506    *       <js>"&amp;p="</js> - The zero-index row number of the first row to display.
507    *       <br>Example: <js>"&amp;p=100"</js>
508    *    <li>
509    *       <js>"&amp;l="</js> - The number of rows to return.
510    *       <br><code>0</code> implies return all rows.
511    *       <br>Example: <js>"&amp;l=100"</js>
512    *    <li>
513    *       <js>"&amp;i="</js> - The case-insensitive search flag.
514    *       <br>Example: <js>"&amp;i=true"</js>
515    * </ul>
516    * 
517    * <h5 class='section'>Notes:</h5>
518    * <ul class='spaced-list'>
519    *    <li>
520    *       Whitespace is trimmed in the parameters.
521    * </ul>
522    * 
523    * @return
524    *    A new {@link SearchArgs} object initialized with the special search query arguments.
525    *    <br>Returns <jk>null</jk> if no search arguments were found.
526    */
527   public SearchArgs getSearchArgs() {
528      if (hasAny("s","v","o","p","l","i")) {
529         return new SearchArgs.Builder()
530            .search(getString("s"))
531            .view(getString("v"))
532            .sort(getString("o"))
533            .position(getInt("p"))
534            .limit(getInt("l"))
535            .ignoreCase(getBoolean("i"))
536            .build();
537      }
538      return null;
539   }
540
541   /**
542    * Returns <jk>true</jk> if the query parameters contains any of the specified names.
543    * 
544    * @param paramNames The parameter names to check for.
545    * @return <jk>true</jk> if the query parameters contains any of the specified names.
546    */
547   public boolean hasAny(String...paramNames) {
548      for (String p : paramNames)
549         if (containsKey(p))
550            return true;
551      return false;
552   }
553
554   /* Workhorse method */
555   private <T> T parse(HttpPartParser parser, String name, Object def, ClassMeta<T> cm) throws ParseException {
556      String val = getString(name);
557      if (val == null)
558         return (T)def;
559      return parseValue(parser, val, cm);
560   }
561
562   /* Workhorse method */
563   private <T> T parse(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException {
564      String val = getString(name);
565      if (cm.isPrimitive() && (val == null || val.isEmpty()))
566         return cm.getPrimitiveDefault();
567      return parseValue(parser, val, cm);
568   }
569
570   /* Workhorse method */
571   @SuppressWarnings("rawtypes")
572   private <T> T parseAll(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException {
573      String[] p = get(name);
574      if (p == null)
575         return null;
576      if (cm.isArray()) {
577         List c = new ArrayList();
578         for (int i = 0; i < p.length; i++)
579            c.add(parseValue(parser, p[i], cm.getElementType()));
580         return (T)toArray(c, cm.getElementType().getInnerClass());
581      } else if (cm.isCollection()) {
582         try {
583            Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList());
584            for (int i = 0; i < p.length; i++)
585               c.add(parseValue(parser, p[i], cm.getElementType()));
586            return (T)c;
587         } catch (ParseException e) {
588            throw e;
589         } catch (Exception e) {
590            // Typically an instantiation exception.
591            throw new ParseException(e);
592         }
593      }
594      throw new ParseException("Invalid call to getQueryParameters(String, ClassMeta).  Class type must be a Collection or array.");
595   }
596
597   private <T> T parseValue(HttpPartParser parser, String val, ClassMeta<T> c) throws ParseException {
598      if (parser == null)
599         parser = this.parser;
600      return parser.parse(HttpPartType.QUERY, val, c);
601   }
602
603   /**
604    * Converts the query parameters to a readable string.
605    * 
606    * @param sorted Sort the query parameters by name.
607    * @return A JSON string containing the contents of the query parameters.
608    */
609   public String toString(boolean sorted) {
610      Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>());
611      for (Map.Entry<String,String[]> e : this.entrySet()) {
612         String[] v = e.getValue();
613         m.put(e.getKey(), v.length == 1 ? v[0] : v);
614      }
615      return JsonSerializer.DEFAULT_LAX.toString(m);
616   }
617
618   /**
619    * Converts this object to a query string.
620    * 
621    * <p>
622    * Returned query string does not start with <js>'?'</js>.
623    * 
624    * @return A new query string, or an empty string if this object is empty.
625    */
626   public String toQueryString() {
627      StringBuilder sb = new StringBuilder();
628      for (Map.Entry<String,String[]> e : this.entrySet()) {
629         for (int i = 0; i < e.getValue().length; i++) {
630            if (sb.length() > 0)
631               sb.append("&");
632            sb.append(urlEncode(e.getKey())).append('=').append(urlEncode(e.getValue()[i]));
633         }
634      }
635      return sb.toString();
636   }
637
638   private ClassMeta<?> getClassMeta(Type type, Type...args) {
639      return beanSession.getClassMeta(type, args);
640   }
641
642   private <T> ClassMeta<T> getClassMeta(Class<T> type) {
643      return beanSession.getClassMeta(type);
644   }
645
646   @Override /* Object */
647   public String toString() {
648      return toString(false);
649   }
650}