Coverage Report - com.jcabi.http.request.BaseRequest
 
Classes in this File Line Coverage Branch Coverage Complexity
BaseRequest
80%
51/63
52%
26/50
1.477
BaseRequest$BaseURI
90%
20/22
14%
2/14
1.477
BaseRequest$FormEncodedBody
62%
15/24
0%
0/8
1.477
BaseRequest$MultipartFormBody
27%
5/18
0%
0/2
1.477
 
 1  
 /**
 2  
  * Copyright (c) 2011-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.http.request;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.aspects.Loggable;
 34  
 import com.jcabi.http.ImmutableHeader;
 35  
 import com.jcabi.http.Request;
 36  
 import com.jcabi.http.RequestBody;
 37  
 import com.jcabi.http.RequestURI;
 38  
 import com.jcabi.http.Response;
 39  
 import com.jcabi.http.Wire;
 40  
 import com.jcabi.immutable.Array;
 41  
 import com.jcabi.log.Logger;
 42  
 import java.io.ByteArrayInputStream;
 43  
 import java.io.IOException;
 44  
 import java.io.InputStream;
 45  
 import java.io.StringWriter;
 46  
 import java.io.UnsupportedEncodingException;
 47  
 import java.lang.reflect.Constructor;
 48  
 import java.lang.reflect.InvocationTargetException;
 49  
 import java.net.URI;
 50  
 import java.net.URLEncoder;
 51  
 import java.nio.charset.Charset;
 52  
 import java.util.Collection;
 53  
 import java.util.LinkedList;
 54  
 import java.util.Map;
 55  
 import javax.json.Json;
 56  
 import javax.json.JsonStructure;
 57  
 import javax.ws.rs.core.UriBuilder;
 58  
 import lombok.EqualsAndHashCode;
 59  
 
 60  
 /**
 61  
  * Base implementation of {@link Request}.
 62  
  *
 63  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 64  
  * @version $Id: 1e2ded7b38832b44b073b645de54d0a7206e94ad $
 65  
  * @since 0.8
 66  
  * @checkstyle ClassDataAbstractionCoupling (500 lines)
 67  
  * @see Request
 68  
  * @see Response
 69  
  * @todo #87:30min Refactor this class to get rid of PMD.GodClass.
 70  
  *  This can be done if MultiPartFormBody and
 71  
  *  FormEncodedBody are pulled out. Also, the two
 72  
  *  share the same implementations for all methods besides formParam,
 73  
  *  so they can be refactored to extend an AbstractRequestBody.
 74  
  *  PMD.TooManyMethods might come together with getting rid of the
 75  
  *  first one, since maybe qulice is counting the methods in the inner
 76  
  *  classes too - if it doesn't, then it can be left.
 77  
  */
 78  
 @Immutable
 79  80
 @EqualsAndHashCode(of = { "home", "mtd", "hdrs", "content" })
 80  
 @Loggable(Loggable.DEBUG)
 81  
 @SuppressWarnings({"PMD.TooManyMethods", "PMD.GodClass"})
 82  493
 final class BaseRequest implements Request {
 83  
 
 84  
     /**
 85  
      * The encoding to use.
 86  
      */
 87  
     private static final String ENCODING = "UTF-8";
 88  
 
 89  
     /**
 90  
      * The Charset to use.
 91  
      * @checkstyle ConstantUsageCheck (3 lines)
 92  
      */
 93  1
     private static final Charset CHARSET =
 94  
         Charset.forName(BaseRequest.ENCODING);
 95  
 
 96  
     /**
 97  
      * An empty immutable {@code byte} array.
 98  
      */
 99  1
     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
 100  
 
 101  
     /**
 102  
      * Wire to use.
 103  
      */
 104  
     private final transient Wire wire;
 105  
 
 106  
     /**
 107  
      * Request URI.
 108  
      */
 109  
     private final transient String home;
 110  
 
 111  
     /**
 112  
      * Method to use.
 113  
      */
 114  
     private final transient String mtd;
 115  
 
 116  
     /**
 117  
      * Socket timeout to use.
 118  
      */
 119  
     private final transient int connect;
 120  
 
 121  
     /**
 122  
      * Read timeout to use.
 123  
      */
 124  
     private final transient int read;
 125  
 
 126  
     /**
 127  
      * Headers.
 128  
      */
 129  
     private final transient Array<Map.Entry<String, String>> hdrs;
 130  
 
 131  
     /**
 132  
      * Body to use.
 133  
      */
 134  
     @Immutable.Array
 135  
     private final transient byte[] content;
 136  
 
 137  
     /**
 138  
      * Public ctor.
 139  
      * @param wre Wire
 140  
      * @param uri The resource to work with
 141  
      */
 142  
     BaseRequest(final Wire wre, final String uri) {
 143  165
         this(
 144  
             wre, uri,
 145  
             new Array<Map.Entry<String, String>>(),
 146  
             Request.GET, BaseRequest.EMPTY_BYTE_ARRAY
 147  
         );
 148  165
     }
 149  
 
 150  
     /**
 151  
      * Public ctor.
 152  
      * @param wre Wire
 153  
      * @param uri The resource to work with
 154  
      * @param headers Headers
 155  
      * @param method HTTP method
 156  
      * @param body HTTP request body
 157  
      * @checkstyle ParameterNumber (5 lines)
 158  
      */
 159  
     BaseRequest(final Wire wre, final String uri,
 160  
         final Iterable<Map.Entry<String, String>> headers,
 161  
         final String method, final byte[] body) {
 162  194
         this(wre, uri, headers, method, body, 0, 0);
 163  194
     }
 164  
 
 165  
     /**
 166  
      * Public ctor.
 167  
      * @param wre Wire
 168  
      * @param uri The resource to work with
 169  
      * @param headers Headers
 170  
      * @param method HTTP method
 171  
      * @param body HTTP request body
 172  
      * @param cnct Connect timeout for http connection
 173  
      * @param rdd Read timeout for http connection
 174  
      * @checkstyle ParameterNumber (5 lines)
 175  
      */
 176  
     BaseRequest(final Wire wre, final String uri,
 177  
         final Iterable<Map.Entry<String, String>> headers,
 178  
         final String method, final byte[] body,
 179  370
         final int cnct, final int rdd) {
 180  369
         this.wire = wre;
 181  370
         URI addr = URI.create(uri);
 182  369
         if (addr.getPath() != null && addr.getPath().isEmpty()) {
 183  21
             addr = UriBuilder.fromUri(addr).path("/").build();
 184  
         }
 185  367
         this.home = addr.toString();
 186  367
         this.hdrs = new Array<Map.Entry<String, String>>(headers);
 187  367
         this.mtd = method;
 188  367
         this.content = body.clone();
 189  369
         this.connect = cnct;
 190  367
         this.read = rdd;
 191  367
     }
 192  
 
 193  
     @Override
 194  
     public RequestURI uri() {
 195  274
         return new BaseRequest.BaseURI(this, this.home);
 196  
     }
 197  
 
 198  
     @Override
 199  
     public Request header(final String name, final Object value) {
 200  32
         return new BaseRequest(
 201  
             this.wire,
 202  
             this.home,
 203  
             this.hdrs.with(new ImmutableHeader(name, value.toString())),
 204  
             this.mtd,
 205  
             this.content,
 206  
             this.connect,
 207  
             this.read
 208  
         );
 209  
     }
 210  
 
 211  
     @Override
 212  
     public Request reset(final String name) {
 213  2
         final Collection<Map.Entry<String, String>> headers =
 214  
             new LinkedList<>();
 215  2
         final String key = ImmutableHeader.normalize(name);
 216  2
         for (final Map.Entry<String, String> header : this.hdrs) {
 217  0
             if (!header.getKey().equals(key)) {
 218  0
                 headers.add(header);
 219  
             }
 220  0
         }
 221  2
         return new BaseRequest(
 222  
             this.wire,
 223  
             this.home,
 224  
             headers,
 225  
             this.mtd,
 226  
             this.content,
 227  
             this.connect,
 228  
             this.read
 229  
         );
 230  
     }
 231  
 
 232  
     @Override
 233  
     public RequestBody body() {
 234  17
         return new BaseRequest.FormEncodedBody(this, this.content);
 235  
     }
 236  
 
 237  
     @Override
 238  
     public RequestBody multipartBody() {
 239  2
         return new BaseRequest.MultipartFormBody(this, this.content);
 240  
     }
 241  
 
 242  
     @Override
 243  
     public Request method(final String method) {
 244  59
         return new BaseRequest(
 245  
             this.wire,
 246  
             this.home,
 247  
             this.hdrs,
 248  
             method,
 249  
             this.content,
 250  
             this.connect,
 251  
             this.read
 252  
         );
 253  
     }
 254  
 
 255  
     @Override
 256  
     public Request timeout(final int cnct, final int rdd) {
 257  12
         return new BaseRequest(
 258  
             this.wire,
 259  
             this.home,
 260  
             this.hdrs,
 261  
             this.mtd,
 262  
             this.content,
 263  
             cnct,
 264  
             rdd
 265  
         );
 266  
     }
 267  
 
 268  
     @Override
 269  
     public Response fetch() throws IOException {
 270  166
         return this.fetchResponse(new ByteArrayInputStream(this.content));
 271  
     }
 272  
 
 273  
     @Override
 274  
     public Response fetch(final InputStream stream) throws IOException {
 275  4
         if (this.content.length > 0) {
 276  2
             throw new IllegalStateException(
 277  
                 "Request Body is not empty, use fetch() instead"
 278  
             );
 279  
         }
 280  2
         return this.fetchResponse(stream);
 281  
     }
 282  
 
 283  
     @Override
 284  
     public <T extends Wire> Request through(final Class<T> type,
 285  
         final Object... args) {
 286  54
         Constructor<?> ctor = null;
 287  58
         for (final Constructor<?> opt : type.getDeclaredConstructors()) {
 288  58
             if (opt.getParameterTypes().length == args.length + 1) {
 289  54
                 ctor = opt;
 290  54
                 break;
 291  
             }
 292  
         }
 293  54
         if (ctor == null) {
 294  0
             throw new IllegalArgumentException(
 295  
                 String.format(
 296  
                     "class %s doesn't have a ctor with %d argument(s)",
 297  
                     type.getName(), args.length
 298  
                 )
 299  
             );
 300  
         }
 301  54
         final Object[] params = new Object[args.length + 1];
 302  54
         params[0] = this.wire;
 303  54
         System.arraycopy(args, 0, params, 1, args.length);
 304  
         final Wire decorated;
 305  
         try {
 306  54
             decorated = Wire.class.cast(ctor.newInstance(params));
 307  0
         } catch (final InstantiationException
 308  
             | IllegalAccessException | InvocationTargetException ex) {
 309  0
             throw new IllegalStateException(ex);
 310  53
         }
 311  54
         return new BaseRequest(
 312  
             decorated,
 313  
             this.home,
 314  
             this.hdrs,
 315  
             this.mtd,
 316  
             this.content,
 317  
             this.connect,
 318  
             this.read
 319  
         );
 320  
     }
 321  
 
 322  
     @Override
 323  
     @SuppressWarnings("PMD.ConsecutiveLiteralAppends")
 324  
     public String toString() {
 325  0
         final URI uri = URI.create(this.home);
 326  0
         final StringBuilder text = new StringBuilder("HTTP/1.1 ")
 327  
             .append(this.mtd).append(' ')
 328  
             .append(uri.getPath())
 329  
             .append(" (")
 330  
             .append(uri.getHost())
 331  
             .append(")\n");
 332  0
         for (final Map.Entry<String, String> header : this.hdrs) {
 333  0
             text.append(
 334  
                 Logger.format(
 335  
                     "%s: %s\n",
 336  
                     header.getKey(),
 337  
                     header.getValue()
 338  
                 )
 339  
             );
 340  0
         }
 341  0
         return text.append('\n')
 342  
             .append(new RequestBody.Printable(this.content).toString())
 343  
             .toString();
 344  
     }
 345  
 
 346  
     /**
 347  
      * Fetch response from server.
 348  
      * @param stream The content to send.
 349  
      * @return The obtained response
 350  
      * @throws IOException If an IO exception occurs.
 351  
      */
 352  
     private Response fetchResponse(final InputStream stream)
 353  
         throws IOException {
 354  167
         final long start = System.currentTimeMillis();
 355  168
         final Response response = this.wire.send(
 356  
             this, this.home, this.mtd,
 357  
             this.hdrs, stream, this.connect,
 358  
             this.read
 359  
         );
 360  168
         final URI uri = URI.create(this.home);
 361  168
         Logger.info(
 362  
             this,
 363  
             "#fetch(%s %s%s %s): [%d %s] in %[ms]s",
 364  
             this.mtd,
 365  
             uri.getHost(),
 366  
             // @checkstyle AvoidInlineConditionalsCheck (1 line)
 367  
             uri.getPort() > 0 ? String.format(":%d", uri.getPort()) : "",
 368  
             uri.getPath(),
 369  
             response.status(),
 370  
             response.reason(),
 371  
             System.currentTimeMillis() - start
 372  
         );
 373  167
         return response;
 374  
     }
 375  
 
 376  
     /**
 377  
      * Base URI.
 378  
      */
 379  
     @Immutable
 380  0
     @EqualsAndHashCode(of = "address")
 381  
     @Loggable(Loggable.DEBUG)
 382  
     private static final class BaseURI implements RequestURI {
 383  
         /**
 384  
          * URI encapsulated.
 385  
          */
 386  
         private final transient String address;
 387  
         /**
 388  
          * Base request encapsulated.
 389  
          */
 390  
         private final transient BaseRequest owner;
 391  
         /**
 392  
          * Public ctor.
 393  
          * @param req Request
 394  
          * @param uri The URI to start with
 395  
          */
 396  308
         BaseURI(final BaseRequest req, final String uri) {
 397  309
             this.owner = req;
 398  309
             this.address = uri;
 399  308
         }
 400  
         @Override
 401  
         public String toString() {
 402  0
             return this.address;
 403  
         }
 404  
         @Override
 405  
         public Request back() {
 406  29
             return new BaseRequest(
 407  
                 this.owner.wire,
 408  
                 this.address,
 409  
                 this.owner.hdrs,
 410  
                 this.owner.mtd,
 411  
                 this.owner.content
 412  
             );
 413  
         }
 414  
         @Override
 415  
         public URI get() {
 416  245
             return URI.create(this.owner.home);
 417  
         }
 418  
         @Override
 419  
         public RequestURI set(final URI uri) {
 420  9
             return new BaseRequest.BaseURI(this.owner, uri.toString());
 421  
         }
 422  
         @Override
 423  
         public RequestURI queryParam(final String name, final Object value) {
 424  5
             return new BaseRequest.BaseURI(
 425  
                 this.owner,
 426  
                 UriBuilder.fromUri(this.address)
 427  
                     .queryParam(name, "{value}")
 428  
                     .build(value).toString()
 429  
             );
 430  
         }
 431  
         @Override
 432  
         public RequestURI queryParams(final Map<String, String> map) {
 433  1
             final UriBuilder uri = UriBuilder.fromUri(this.address);
 434  1
             final Object[] values = new Object[map.size()];
 435  1
             int idx = 0;
 436  1
             for (final Map.Entry<String, String> pair : map.entrySet()) {
 437  1
                 uri.queryParam(pair.getKey(), String.format("{x%d}", idx));
 438  1
                 values[idx] = pair.getValue();
 439  1
                 ++idx;
 440  1
             }
 441  1
             return new BaseRequest.BaseURI(
 442  
                 this.owner,
 443  
                 uri.build(values).toString()
 444  
             );
 445  
         }
 446  
         @Override
 447  
         public RequestURI path(final String segment) {
 448  17
             return new BaseRequest.BaseURI(
 449  
                 this.owner,
 450  
                 UriBuilder.fromUri(this.address)
 451  
                     .path(segment)
 452  
                     .build().toString()
 453  
             );
 454  
         }
 455  
         @Override
 456  
         public RequestURI userInfo(final String info) {
 457  1
             return new BaseRequest.BaseURI(
 458  
                 this.owner,
 459  
                 UriBuilder.fromUri(this.address)
 460  
                     .userInfo(info)
 461  
                     .build().toString()
 462  
             );
 463  
         }
 464  
         @Override
 465  
         public RequestURI port(final int num) {
 466  1
             return new BaseRequest.BaseURI(
 467  
                 this.owner,
 468  
                 UriBuilder.fromUri(this.address)
 469  
                     .port(num).build().toString()
 470  
             );
 471  
         }
 472  
     }
 473  
 
 474  
     /**
 475  
      * Body of a request with a form that has attachments.
 476  
      * @todo #87:1h Implement and unit test method formParam(String, Object)
 477  
      *  Details <a href="http://stackoverflow.com/
 478  
      *  questions/8659808/how-does-http-file-upload-work">here</a>
 479  
      *  (second answer). <br> e.g. While FormEncodedBody.formParam adds
 480  
      *  the param to a body with enctype application/x-www-form-urlencoded,
 481  
      *  method MultipartFormBody.formParam should add it to a body with
 482  
      *  enctype multipart/form-data.
 483  
      */
 484  
     private static final class MultipartFormBody implements RequestBody {
 485  
 
 486  
         /**
 487  
          * Content encapsulated.
 488  
          */
 489  
         @Immutable.Array
 490  
         private final transient byte[] text;
 491  
 
 492  
         /**
 493  
          * Base request encapsulated.
 494  
          */
 495  
         private final transient BaseRequest owner;
 496  
 
 497  
         /**
 498  
          * Public ctor.
 499  
          * @param req Request
 500  
          * @param body Text to encapsulate
 501  
          */
 502  2
         MultipartFormBody(final BaseRequest req, final byte[] body) {
 503  2
             this.owner = req;
 504  2
             this.text = body.clone();
 505  2
         }
 506  
 
 507  
         @Override
 508  
         public String toString() {
 509  0
             return new RequestBody.Printable(this.text).toString();
 510  
         }
 511  
 
 512  
         @Override
 513  
         public Request back() {
 514  2
             return new BaseRequest(
 515  
                 this.owner.wire,
 516  
                 this.owner.home,
 517  
                 this.owner.hdrs,
 518  
                 this.owner.mtd,
 519  
                 this.text,
 520  
                 this.owner.connect,
 521  
                 this.owner.read
 522  
             );
 523  
         }
 524  
 
 525  
         @Override
 526  
         public String get() {
 527  0
             return new String(this.text, BaseRequest.CHARSET);
 528  
         }
 529  
 
 530  
         @Override
 531  
         public RequestBody set(final String txt) {
 532  0
             return this.set(txt.getBytes(BaseRequest.CHARSET));
 533  
         }
 534  
 
 535  
         @Override
 536  
         public RequestBody set(final JsonStructure json) {
 537  0
             final StringWriter writer = new StringWriter();
 538  0
             Json.createWriter(writer).write(json);
 539  0
             return this.set(writer.toString());
 540  
         }
 541  
 
 542  
         @Override
 543  
         public RequestBody set(final byte[] txt) {
 544  0
             return new BaseRequest.FormEncodedBody(this.owner, txt);
 545  
         }
 546  
 
 547  
         @Override
 548  
         public RequestBody formParam(final String name, final Object value) {
 549  0
             throw new UnsupportedOperationException("Method not available");
 550  
         }
 551  
 
 552  
         @Override
 553  
         public RequestBody formParams(final Map<String, String> params) {
 554  0
             RequestBody body = this;
 555  0
             for (final Map.Entry<String, String> param : params.entrySet()) {
 556  0
                 body = body.formParam(param.getKey(), param.getValue());
 557  0
             }
 558  0
             return body;
 559  
         }
 560  
     }
 561  
 
 562  
     /**
 563  
      * Body of a request with a simple form.
 564  
      * (enctype application/x-www-form-urlencoded)
 565  
      */
 566  
     @Immutable
 567  0
     @EqualsAndHashCode(of = "text")
 568  
     @Loggable(Loggable.DEBUG)
 569  
     private static final class FormEncodedBody implements RequestBody {
 570  
 
 571  
         /**
 572  
          * Content encapsulated.
 573  
          */
 574  
         @Immutable.Array
 575  
         private final transient byte[] text;
 576  
 
 577  
         /**
 578  
          * Base request encapsulated.
 579  
          */
 580  
         private final transient BaseRequest owner;
 581  
 
 582  
         /**
 583  
          * URL form character to prepend.
 584  
          */
 585  
         private final transient String prepend;
 586  
 
 587  
         /**
 588  
          * Public ctor.
 589  
          * @param req Request
 590  
          * @param body Text to encapsulate
 591  
          */
 592  
         FormEncodedBody(final BaseRequest req, final byte[] body) {
 593  28
             this(req, body, "");
 594  28
         }
 595  
 
 596  
         /**
 597  
          * Public ctor.
 598  
          * @param req Request
 599  
          * @param body Text to encapsulate
 600  
          * @param pre Character to prepend
 601  
          */
 602  
         FormEncodedBody(
 603  
             final BaseRequest req, final byte[] body, final String pre
 604  34
         ) {
 605  34
             this.owner = req;
 606  34
             this.text = body.clone();
 607  34
             this.prepend = pre;
 608  34
         }
 609  
 
 610  
         @Override
 611  
         public String toString() {
 612  0
             return new RequestBody.Printable(this.text).toString();
 613  
         }
 614  
 
 615  
         @Override
 616  
         public Request back() {
 617  16
             return new BaseRequest(
 618  
                 this.owner.wire,
 619  
                 this.owner.home,
 620  
                 this.owner.hdrs,
 621  
                 this.owner.mtd,
 622  
                 this.text,
 623  
                 this.owner.connect,
 624  
                 this.owner.read
 625  
             );
 626  
         }
 627  
 
 628  
         @Override
 629  
         public String get() {
 630  7
             return new String(this.text, BaseRequest.CHARSET);
 631  
         }
 632  
 
 633  
         @Override
 634  
         public RequestBody set(final String txt) {
 635  11
             return this.set(txt.getBytes(BaseRequest.CHARSET));
 636  
         }
 637  
 
 638  
         @Override
 639  
         public RequestBody set(final JsonStructure json) {
 640  1
             final StringWriter writer = new StringWriter();
 641  1
             Json.createWriter(writer).write(json);
 642  1
             return this.set(writer.toString());
 643  
         }
 644  
 
 645  
         @Override
 646  
         public RequestBody set(final byte[] txt) {
 647  11
             return new BaseRequest.FormEncodedBody(this.owner, txt);
 648  
         }
 649  
 
 650  
         @Override
 651  
         public RequestBody formParam(final String name, final Object value) {
 652  
             try {
 653  6
                 return new BaseRequest.FormEncodedBody(
 654  
                     this.owner,
 655  
                     new StringBuilder(this.get())
 656  
                         .append(this.prepend)
 657  
                         .append(name)
 658  
                         .append('=')
 659  
                         .append(
 660  
                             URLEncoder.encode(
 661  
                                 value.toString(),
 662  
                                 BaseRequest.ENCODING
 663  
                             )
 664  
                         )
 665  
                         .toString()
 666  
                         .getBytes(BaseRequest.CHARSET),
 667  
                     "&"
 668  
                 );
 669  0
             } catch (final UnsupportedEncodingException ex) {
 670  0
                 throw new IllegalStateException(ex);
 671  
             }
 672  
         }
 673  
 
 674  
         @Override
 675  
         public RequestBody formParams(final Map<String, String> params) {
 676  0
             RequestBody body = this;
 677  0
             for (final Map.Entry<String, String> param : params.entrySet()) {
 678  0
                 body = body.formParam(param.getKey(), param.getValue());
 679  0
             }
 680  0
             return body;
 681  
         }
 682  
 
 683  
     }
 684  
 
 685  
 }