gt:     final String baseUrl = url.toString();
context: /*
   * Copyright (c) 2011 Kevin Sawicki <kevinsawicki@gmail.com>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to
   * deal in the Software without restriction, including without limitation the
   * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   * sell copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
   * IN THE SOFTWARE.
   */
  package com.noknown.framework.common.util.http;
  
  import javax.net.ssl.*;
  import java.io.*;
  import java.net.*;
  import java.nio.ByteBuffer;
  import java.nio.CharBuffer;
  import java.nio.charset.Charset;
  import java.nio.charset.CharsetEncoder;
  import java.security.AccessController;
  import java.security.GeneralSecurityException;
  import java.security.PrivilegedAction;
  import java.security.SecureRandom;
  import java.security.cert.X509Certificate;
  import java.util.*;
  import java.util.Map.Entry;
  import java.util.concurrent.Callable;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.zip.GZIPInputStream;
  
  import static java.net.HttpURLConnection.*;
  
  /**
   * A fluid interface for making HTTP requests using an underlying
   * {@link HttpURLConnection} (or sub-class).
   * <p>
   * Each instance supports making a single request and cannot be reused for
   * further requests.
   */
  public class HttpRequest {
  
    /**
     * 'UTF-8' charset name
     */
    public static final String CHARSET_UTF8 = "UTF-8";
  
    /**
     * 'Accept' header name
     */
    public static final String HEADER_ACCEPT = "Accept";
  
    /**
     * 'Accept-Charset' header name
     */
    public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset";
  
    /**
     * 'Accept-Encoding' header name
     */
    public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
  
    /**
     * 'Authorization' header name
     */
    public static final String HEADER_AUTHORIZATION = "Authorization";
  
    /**
     * 'Cache-Control' header name
     */
    public static final String HEADER_CACHE_CONTROL = "Cache-Control";
  
    /**
     * 'Content-Encoding' header name
     */
    public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
  
    /**
     * 'Content-Length' header name
     */
    public static final String HEADER_CONTENT_LENGTH = "Content-Length";
  
    /**
     * 'Content-Type' header name
     */
    public static final String HEADER_CONTENT_TYPE = "Content-Type";
  
    /**
     * 'Date' header name
     */
    public static final String HEADER_DATE = "Date";
  
    /**
     * 'ETag' header name
     */
    public static final String HEADER_ETAG = "ETag";
  
    /**
     * 'Expires' header name
     */
    public static final String HEADER_EXPIRES = "Expires";
  
    /**
     * 'If-None-Match' header name
     */
    public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
  
    /**
     * 'Last-Modified' header name
     */
    public static final String HEADER_LAST_MODIFIED = "Last-Modified";
  
    /**
     * 'Location' header name
     */
    public static final String HEADER_LOCATION = "Location";
  
    /**
     * 'Server' header name
     */
    public static final String HEADER_SERVER = "Server";
  
    /**
     * 'User-Agent' header name
     */
    public static final String HEADER_USER_AGENT = "User-Agent";
  
    /**
     * 'DELETE' request method
     */
    public static final String METHOD_DELETE = "DELETE";
  
    /**
     * 'GET' request method
     */
    public static final String METHOD_GET = "GET";
  
    /**
     * 'HEAD' request method
     */
    public static final String METHOD_HEAD = "HEAD";
  
    /**
     * 'OPTIONS' options method
     */
    public static final String METHOD_OPTIONS = "OPTIONS";
  
    /**
     * 'POST' request method
     */
    public static final String METHOD_POST = "POST";
  
    /**
     * 'PUT' request method
     */
    public static final String METHOD_PUT = "PUT";
  
    /**
     * 'TRACE' request method
     */
    public static final String METHOD_TRACE = "TRACE";
  
    /**
     * 'charset' header value parameter
     */
    public static final String PARAM_CHARSET = "charset";
  
    private static final String BOUNDARY = "00content0boundary00";
  
    private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
        + BOUNDARY;
  
    private static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
  
    private static final String CONTENT_TYPE_JSON = "application/json";
  
    private static final String ENCODING_GZIP = "gzip";
  
    private static final String[] EMPTY_STRINGS = new String[0];
  
    private static SSLSocketFactory TRUSTED_FACTORY;
  
    private static HostnameVerifier TRUSTED_VERIFIER;
  
    private static String getValidCharset(final String charset) {
  	  if (charset != null) {
  		  return charset;
  	  } else {
  		  return CHARSET_UTF8;
  	  }
    }
  
    private static SSLSocketFactory getTrustedFactory()
        throws HttpRequestException {
      if (TRUSTED_FACTORY == null) {
        final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
  
  	      @Override
  	      public X509Certificate[] getAcceptedIssuers() {
  		      return new X509Certificate[0];
  	      }
  
  	      @Override
  	      public void checkClientTrusted(X509Certificate[] chain, String authType) {
  		      // Intentionally left blank
  	      }
  
  	      @Override
  	      public void checkServerTrusted(X509Certificate[] chain, String authType) {
  		      // Intentionally left blank
  	      }
        } };
        try {
          SSLContext context = SSLContext.getInstance("TLS");
          context.init(null, trustAllCerts, new SecureRandom());
          TRUSTED_FACTORY = context.getSocketFactory();
        } catch (GeneralSecurityException e) {
          IOException ioException = new IOException(
              "Security exception configuring SSL context");
          ioException.initCause(e);
          throw new HttpRequestException(ioException);
        }
      }
  
      return TRUSTED_FACTORY;
    }
  
    private static HostnameVerifier getTrustedVerifier() {
  	  if (TRUSTED_VERIFIER == null) {
  		  TRUSTED_VERIFIER = new HostnameVerifier() {
  
  			  @Override
  			  public boolean verify(String hostname, SSLSession session) {
  				  return true;
  			  }
  		  };
  	  }
  
      return TRUSTED_VERIFIER;
    }
  
    private static StringBuilder addPathSeparator(final String baseUrl,
        final StringBuilder result) {
      // Add trailing slash if the base URL doesn't have any path segments.
      //
      // The following test is checking for the last slash not being part of
      // the protocol to host separator: '://'.
  	  if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) {
  		  result.append('/');
  	  }
      return result;
    }
  
    private static StringBuilder addParamPrefix(final String baseUrl,
        final StringBuilder result) {
      // Add '?' if missing and add '&' if params already exist in base url
      final int queryStart = baseUrl.indexOf('?');
      final int lastChar = result.length() - 1;
  	  if (queryStart == -1) {
  		  result.append('?');
  	  } else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') {
  		  result.append('&');
  	  }
      return result;
    }
  
    /**
     * Encode the given URL as an ASCII {@link String}
     * <p>
     * This method ensures the path and query segments of the URL are properly
     * encoded such as ' ' characters being encoded to '%20' or any UTF-8
     * characters that are non-ASCII. No encoding of URLs is done by default by
     * the {@link HttpRequest} constructors and so if URL encoding is needed this
     * method should be called before calling the {@link HttpRequest} constructor.
     *
     * @param url
     * @return encoded URL
     * @throws HttpRequestException
     */
    public static String encode(final CharSequence url)
        throws HttpRequestException {
      URL parsed;
      try {
        parsed = new URL(url.toString());
      } catch (IOException e) {
        throw new HttpRequestException(e);
      }
  
      String host = parsed.getHost();
      int port = parsed.getPort();
  	  if (port != -1) {
  		  host = host + ':' + Integer.toString(port);
  	  }
  
      try {
        return new URI(parsed.getProtocol(), host, parsed.getPath(),
            parsed.getQuery(), null).toASCIIString();
      } catch (URISyntaxException e) {
        IOException io = new IOException("Parsing URI failed");
        io.initCause(e);
        throw new HttpRequestException(io);
      }
    }
  
  	/**
  	 * HTTP request exception whose cause is always an {@link IOException}
  	 */
  	public static class HttpRequestException extends RuntimeException {
  
  		private static final long serialVersionUID = -1170466989781746231L;
  
  		/**
  		 * @param cause
  		 */
  		protected HttpRequestException(final IOException cause) {
  			super(cause);
  		}
  
  		/**
  		 * Get {@link IOException} that triggered this request exception
  		 *
  		 * @return {@link IOException} cause
  		 */
  		@Override
  		public IOException getCause() {
  			return (IOException) super.getCause();
  		}
  	}
  
    /**
     * Append given map as query parameters to the base URL
     * <p>
     * Each map entry's key will be a parameter name and the value's
     * {@link Object#toString()} will be the parameter value.
     *
     * @param url
     * @param params
     * @return URL with appended query params
     */
    public static String append(final CharSequence url, final Map<?, ?> params) {
      final String baseUrl = url.toString();
  	  if (params == null || params.isEmpty()) {
  	    return baseUrl;
      }
  
      final StringBuilder result = new StringBuilder(baseUrl);
  
      addPathSeparator(baseUrl, result);
      addParamPrefix(baseUrl, result);
  
      Entry<?, ?> entry;
      Object value;
      Iterator<?> iterator = params.entrySet().iterator();
      entry = (Entry<?, ?>) iterator.next();
      result.append(entry.getKey().toString());
      result.append('=');
  	  value = entry.getValue();
  	  if (value != null) {
  		  result.append(value);
      }
  
      while (iterator.hasNext()) {
        result.append('&');
        entry = (Entry<?, ?>) iterator.next();
        result.append(entry.getKey().toString());
        result.append('=');
  	    value = entry.getValue();
  	    if (value != null) {
  		    result.append(value);
        }
      }
  
      return result.toString();
    }
  
    /**
     * Append given name/value pairs as query parameters to the base URL
     * <p>
     * The params argument is interpreted as a sequence of name/value pairs so the
     * given number of params must be divisible by 2.
     *
     * @param url
     * @param params
     *          name/value pairs
     * @return URL with appended query params
     */
    public static String append(final CharSequence url, final String... params) {
  