Coverage Report - com.jcabi.http.request.BaseRequest
 
Classes in this File Line Coverage Branch Coverage Complexity
BaseRequest
71%
45/63
50%
25/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
0%
0/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: 574966080b8f5d8eee2d73334cc9b57209311af3 $
 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  440
 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  150
         this(
 144  
             wre, uri,
 145  
             new Array<Map.Entry<String, String>>(),
 146  
             Request.GET, BaseRequest.EMPTY_BYTE_ARRAY
 147  
         );
 148  150
     }
 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  270
         this(wre, uri, headers, method, body, 0, 0);
 163  270
     }
 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  309
         final int cnct, final int rdd) {
 180  309
         this.wire = wre;
 181  309
         URI addr = URI.create(uri);
 182  309
         if (addr.getPath() != null && addr.getPath().isEmpty()) {
 183  9
             addr = UriBuilder.fromUri(addr).path("/").build();
 184  
         }
 185  309
         this.home = addr.toString();
 186  309
         this.hdrs = new Array<Map.Entry<String, String>>(headers);
 187  309
         this.mtd = method;
 188  309
         this.content = body.clone();
 189  309
         this.connect = cnct;
 190  309
         this.read = rdd;
 191  309
     }
 192  
 
 193  
     @Override
 194  
     public RequestURI uri() {
 195  273
         return new BaseRequest.BaseURI(this, this.home);
 196  
     }
 197  
 
 198  
     @Override
 199  
     public Request header(final String name, final Object value) {
 200  30
         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  
         );
 207  
     }
 208  
 
 209  
     @Override
 210  
     public Request reset(final String name) {
 211  0
         final Collection<Map.Entry<String, String>> headers =
 212  
             new LinkedList<>();
 213  0
         final String key = ImmutableHeader.normalize(name);
 214  0
         for (final Map.Entry<String, String> header : this.hdrs) {
 215  0
             if (!header.getKey().equals(key)) {
 216  0
                 headers.add(header);
 217  
             }
 218  0
         }
 219  0
         return new BaseRequest(
 220  
             this.wire,
 221  
             this.home,
 222  
             headers,
 223  
             this.mtd,
 224  
             this.content
 225  
         );
 226  
     }
 227  
 
 228  
     @Override
 229  
     public RequestBody body() {
 230  15
         return new BaseRequest.FormEncodedBody(this, this.content);
 231  
     }
 232  
 
 233  
     @Override
 234  
     public RequestBody multipartBody() {
 235  0
         return new BaseRequest.MultipartFormBody(this, this.content);
 236  
     }
 237  
 
 238  
     @Override
 239  
     public Request method(final String method) {
 240  47
         return new BaseRequest(
 241  
             this.wire,
 242  
             this.home,
 243  
             this.hdrs,
 244  
             method,
 245  
             this.content
 246  
         );
 247  
     }
 248  
 
 249  
     @Override
 250  
     public Request timeout(final int cnct, final int rdd) {
 251  0
         return new BaseRequest(
 252  
             this.wire,
 253  
             this.home,
 254  
             this.hdrs,
 255  
             this.mtd,
 256  
             this.content,
 257  
             cnct,
 258  
             rdd
 259  
         );
 260  
     }
 261  
 
 262  
     @Override
 263  
     public Response fetch() throws IOException {
 264  151
         return this.fetchResponse(new ByteArrayInputStream(this.content));
 265  
     }
 266  
 
 267  
     @Override
 268  
     public Response fetch(final InputStream stream) throws IOException {
 269  4
         if (this.content.length > 0) {
 270  2
             throw new IllegalStateException(
 271  
                 "Request Body is not empty, use fetch() instead"
 272  
             );
 273  
         }
 274  2
         return this.fetchResponse(stream);
 275  
     }
 276  
 
 277  
     @Override
 278  
     public <T extends Wire> Request through(final Class<T> type,
 279  
         final Object... args) {
 280  39
         Constructor<?> ctor = null;
 281  43
         for (final Constructor<?> opt : type.getDeclaredConstructors()) {
 282  43
             if (opt.getParameterTypes().length == args.length + 1) {
 283  39
                 ctor = opt;
 284  39
                 break;
 285  
             }
 286  
         }
 287  39
         if (ctor == null) {
 288  0
             throw new IllegalArgumentException(
 289  
                 String.format(
 290  
                     "class %s doesn't have a ctor with %d argument(s)",
 291  
                     type.getName(), args.length
 292  
                 )
 293  
             );
 294  
         }
 295  39
         final Object[] params = new Object[args.length + 1];
 296  39
         params[0] = this.wire;
 297  39
         System.arraycopy(args, 0, params, 1, args.length);
 298  
         final Wire decorated;
 299  
         try {
 300  39
             decorated = Wire.class.cast(ctor.newInstance(params));
 301  0
         } catch (final InstantiationException
 302  
             | IllegalAccessException | InvocationTargetException ex) {
 303  0
             throw new IllegalStateException(ex);
 304  39
         }
 305  39
         return new BaseRequest(
 306  
             decorated,
 307  
             this.home,
 308  
             this.hdrs,
 309  
             this.mtd,
 310  
             this.content,
 311  
             this.connect,
 312  
             this.read
 313  
         );
 314  
     }
 315  
 
 316  
     @Override
 317  
     @SuppressWarnings("PMD.ConsecutiveLiteralAppends")
 318  
     public String toString() {
 319  0
         final URI uri = URI.create(this.home);
 320  0
         final StringBuilder text = new StringBuilder("HTTP/1.1 ")
 321  
             .append(this.mtd).append(' ')
 322  
             .append(uri.getPath())
 323  
             .append(" (")
 324  
             .append(uri.getHost())
 325  
             .append(")\n");
 326  0
         for (final Map.Entry<String, String> header : this.hdrs) {
 327  0
             text.append(
 328  
                 Logger.format(
 329  
                     "%s: %s\n",
 330  
                     header.getKey(),
 331  
                     header.getValue()
 332  
                 )
 333  
             );
 334  0
         }
 335  0
         return text.append('\n')
 336  
             .append(new RequestBody.Printable(this.content).toString())
 337  
             .toString();
 338  
     }
 339  
 
 340  
     /**
 341  
      * Fetch response from server.
 342  
      * @param stream The content to send.
 343  
      * @return The obtained response
 344  
      * @throws IOException If an IO exception occurs.
 345  
      */
 346  
     private Response fetchResponse(final InputStream stream)
 347  
         throws IOException {
 348  153
         final long start = System.currentTimeMillis();
 349  153
         final Response response = this.wire.send(
 350  
             this, this.home, this.mtd,
 351  
             this.hdrs, stream, this.connect,
 352  
             this.read
 353  
         );
 354  152
         final URI uri = URI.create(this.home);
 355  153
         Logger.info(
 356  
             this,
 357  
             "#fetch(%s %s%s %s): [%d %s] in %[ms]s",
 358  
             this.mtd,
 359  
             uri.getHost(),
 360  
             // @checkstyle AvoidInlineConditionalsCheck (1 line)
 361  
             uri.getPort() > 0 ? String.format(":%d", uri.getPort()) : "",
 362  
             uri.getPath(),
 363  
             response.status(),
 364  
             response.reason(),
 365  
             System.currentTimeMillis() - start
 366  
         );
 367  153
         return response;
 368  
     }
 369  
 
 370  
     /**
 371  
      * Base URI.
 372  
      */
 373  
     @Immutable
 374  0
     @EqualsAndHashCode(of = "address")
 375  
     @Loggable(Loggable.DEBUG)
 376  
     private static final class BaseURI implements RequestURI {
 377  
         /**
 378  
          * URI encapsulated.
 379  
          */
 380  
         private final transient String address;
 381  
         /**
 382  
          * Base request encapsulated.
 383  
          */
 384  
         private final transient BaseRequest owner;
 385  
         /**
 386  
          * Public ctor.
 387  
          * @param req Request
 388  
          * @param uri The URI to start with
 389  
          */
 390  307
         BaseURI(final BaseRequest req, final String uri) {
 391  307
             this.owner = req;
 392  307
             this.address = uri;
 393  307
         }
 394  
         @Override
 395  
         public String toString() {
 396  0
             return this.address;
 397  
         }
 398  
         @Override
 399  
         public Request back() {
 400  29
             return new BaseRequest(
 401  
                 this.owner.wire,
 402  
                 this.address,
 403  
                 this.owner.hdrs,
 404  
                 this.owner.mtd,
 405  
                 this.owner.content
 406  
             );
 407  
         }
 408  
         @Override
 409  
         public URI get() {
 410  244
             return URI.create(this.owner.home);
 411  
         }
 412  
         @Override
 413  
         public RequestURI set(final URI uri) {
 414  9
             return new BaseRequest.BaseURI(this.owner, uri.toString());
 415  
         }
 416  
         @Override
 417  
         public RequestURI queryParam(final String name, final Object value) {
 418  5
             return new BaseRequest.BaseURI(
 419  
                 this.owner,
 420  
                 UriBuilder.fromUri(this.address)
 421  
                     .queryParam(name, "{value}")
 422  
                     .build(value).toString()
 423  
             );
 424  
         }
 425  
         @Override
 426  
         public RequestURI queryParams(final Map<String, String> map) {
 427  1
             final UriBuilder uri = UriBuilder.fromUri(this.address);
 428  1
             final Object[] values = new Object[map.size()];
 429  1
             int idx = 0;
 430  1
             for (final Map.Entry<String, String> pair : map.entrySet()) {
 431  1
                 uri.queryParam(pair.getKey(), String.format("{x%d}", idx));
 432  1
                 values[idx] = pair.getValue();
 433  1
                 ++idx;
 434  1
             }
 435  1
             return new BaseRequest.BaseURI(
 436  
                 this.owner,
 437  
                 uri.build(values).toString()
 438  
             );
 439  
         }
 440  
         @Override
 441  
         public RequestURI path(final String segment) {
 442  17
             return new BaseRequest.BaseURI(
 443  
                 this.owner,
 444  
                 UriBuilder.fromUri(this.address)
 445  
                     .path(segment)
 446  
                     .build().toString()
 447  
             );
 448  
         }
 449  
         @Override
 450  
         public RequestURI userInfo(final String info) {
 451  1
             return new BaseRequest.BaseURI(
 452  
                 this.owner,
 453  
                 UriBuilder.fromUri(this.address)
 454  
                     .userInfo(info)
 455  
                     .build().toString()
 456  
             );
 457  
         }
 458  
         @Override
 459  
         public RequestURI port(final int num) {
 460  1
             return new BaseRequest.BaseURI(
 461  
                 this.owner,
 462  
                 UriBuilder.fromUri(this.address)
 463  
                     .port(num).build().toString()
 464  
             );
 465  
         }
 466  
     }
 467  
 
 468  
     /**
 469  
      * Body of a request with a form that has attachments.
 470  
      * @todo #87:1h Implement and unit test method formParam(String, Object)
 471  
      *  Details <a href="http://stackoverflow.com/
 472  
      *  questions/8659808/how-does-http-file-upload-work">here</a>
 473  
      *  (second answer). <br> e.g. While FormEncodedBody.formParam adds
 474  
      *  the param to a body with enctype application/x-www-form-urlencoded,
 475  
      *  method MultipartFormBody.formParam should add it to a body with
 476  
      *  enctype multipart/form-data.
 477  
      */
 478  
     private static final class MultipartFormBody implements RequestBody {
 479  
 
 480  
         /**
 481  
          * Content encapsulated.
 482  
          */
 483  
         @Immutable.Array
 484  
         private final transient byte[] text;
 485  
 
 486  
         /**
 487  
          * Base request encapsulated.
 488  
          */
 489  
         private final transient BaseRequest owner;
 490  
 
 491  
         /**
 492  
          * Public ctor.
 493  
          * @param req Request
 494  
          * @param body Text to encapsulate
 495  
          */
 496  0
         MultipartFormBody(final BaseRequest req, final byte[] body) {
 497  0
             this.owner = req;
 498  0
             this.text = body.clone();
 499  0
         }
 500  
 
 501  
         @Override
 502  
         public String toString() {
 503  0
             return new RequestBody.Printable(this.text).toString();
 504  
         }
 505  
 
 506  
         @Override
 507  
         public Request back() {
 508  0
             return new BaseRequest(
 509  
                 this.owner.wire,
 510  
                 this.owner.home,
 511  
                 this.owner.hdrs,
 512  
                 this.owner.mtd,
 513  
                 this.text
 514  
             );
 515  
         }
 516  
 
 517  
         @Override
 518  
         public String get() {
 519  0
             return new String(this.text, BaseRequest.CHARSET);
 520  
         }
 521  
 
 522  
         @Override
 523  
         public RequestBody set(final String txt) {
 524  0
             return this.set(txt.getBytes(BaseRequest.CHARSET));
 525  
         }
 526  
 
 527  
         @Override
 528  
         public RequestBody set(final JsonStructure json) {
 529  0
             final StringWriter writer = new StringWriter();
 530  0
             Json.createWriter(writer).write(json);
 531  0
             return this.set(writer.toString());
 532  
         }
 533  
 
 534  
         @Override
 535  
         public RequestBody set(final byte[] txt) {
 536  0
             return new BaseRequest.FormEncodedBody(this.owner, txt);
 537  
         }
 538  
 
 539  
         @Override
 540  
         public RequestBody formParam(final String name, final Object value) {
 541  0
             throw new UnsupportedOperationException("Method not available");
 542  
         }
 543  
 
 544  
         @Override
 545  
         public RequestBody formParams(final Map<String, String> params) {
 546  0
             RequestBody body = this;
 547  0
             for (final Map.Entry<String, String> param : params.entrySet()) {
 548  0
                 body = body.formParam(param.getKey(), param.getValue());
 549  0
             }
 550  0
             return body;
 551  
         }
 552  
     }
 553  
 
 554  
     /**
 555  
      * Body of a request with a simple form.
 556  
      * (enctype application/x-www-form-urlencoded)
 557  
      */
 558  
     @Immutable
 559  0
     @EqualsAndHashCode(of = "text")
 560  
     @Loggable(Loggable.DEBUG)
 561  
     private static final class FormEncodedBody implements RequestBody {
 562  
 
 563  
         /**
 564  
          * Content encapsulated.
 565  
          */
 566  
         @Immutable.Array
 567  
         private final transient byte[] text;
 568  
 
 569  
         /**
 570  
          * Base request encapsulated.
 571  
          */
 572  
         private final transient BaseRequest owner;
 573  
 
 574  
         /**
 575  
          * URL form character to prepend.
 576  
          */
 577  
         private final transient String prepend;
 578  
 
 579  
         /**
 580  
          * Public ctor.
 581  
          * @param req Request
 582  
          * @param body Text to encapsulate
 583  
          */
 584  
         FormEncodedBody(final BaseRequest req, final byte[] body) {
 585  26
             this(req, body, "");
 586  26
         }
 587  
 
 588  
         /**
 589  
          * Public ctor.
 590  
          * @param req Request
 591  
          * @param body Text to encapsulate
 592  
          * @param pre Character to prepend
 593  
          */
 594  
         FormEncodedBody(
 595  
             final BaseRequest req, final byte[] body, final String pre
 596  32
         ) {
 597  32
             this.owner = req;
 598  32
             this.text = body.clone();
 599  32
             this.prepend = pre;
 600  32
         }
 601  
 
 602  
         @Override
 603  
         public String toString() {
 604  0
             return new RequestBody.Printable(this.text).toString();
 605  
         }
 606  
 
 607  
         @Override
 608  
         public Request back() {
 609  14
             return new BaseRequest(
 610  
                 this.owner.wire,
 611  
                 this.owner.home,
 612  
                 this.owner.hdrs,
 613  
                 this.owner.mtd,
 614  
                 this.text
 615  
             );
 616  
         }
 617  
 
 618  
         @Override
 619  
         public String get() {
 620  7
             return new String(this.text, BaseRequest.CHARSET);
 621  
         }
 622  
 
 623  
         @Override
 624  
         public RequestBody set(final String txt) {
 625  11
             return this.set(txt.getBytes(BaseRequest.CHARSET));
 626  
         }
 627  
 
 628  
         @Override
 629  
         public RequestBody set(final JsonStructure json) {
 630  1
             final StringWriter writer = new StringWriter();
 631  1
             Json.createWriter(writer).write(json);
 632  1
             return this.set(writer.toString());
 633  
         }
 634  
 
 635  
         @Override
 636  
         public RequestBody set(final byte[] txt) {
 637  11
             return new BaseRequest.FormEncodedBody(this.owner, txt);
 638  
         }
 639  
 
 640  
         @Override
 641  
         public RequestBody formParam(final String name, final Object value) {
 642  
             try {
 643  6
                 return new BaseRequest.FormEncodedBody(
 644  
                     this.owner,
 645  
                     new StringBuilder(this.get())
 646  
                         .append(this.prepend)
 647  
                         .append(name)
 648  
                         .append('=')
 649  
                         .append(
 650  
                             URLEncoder.encode(
 651  
                                 value.toString(),
 652  
                                 BaseRequest.ENCODING
 653  
                             )
 654  
                         )
 655  
                         .toString()
 656  
                         .getBytes(BaseRequest.CHARSET),
 657  
                     "&"
 658  
                 );
 659  0
             } catch (final UnsupportedEncodingException ex) {
 660  0
                 throw new IllegalStateException(ex);
 661  
             }
 662  
         }
 663  
 
 664  
         @Override
 665  
         public RequestBody formParams(final Map<String, String> params) {
 666  0
             RequestBody body = this;
 667  0
             for (final Map.Entry<String, String> param : params.entrySet()) {
 668  0
                 body = body.formParam(param.getKey(), param.getValue());
 669  0
             }
 670  0
             return body;
 671  
         }
 672  
 
 673  
     }
 674  
 
 675  
 }