code: protected String getCanonicalizedQueryString(Map<String, List<String>> parameters) {
  
          final SortedMap<String, List<String>> sorted = new TreeMap<String, List<String>>();
  
          /**
           * Signing protocol expects the param values also to be sorted after url
           * encoding in addition to sorted parameter names.
           */
          for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
              final String encodedParamName = SdkHttpUtils.urlEncode(
                      entry.getKey(), false);
              final List<String> paramValues = entry.getValue();
              final List<String> encodedValues = new ArrayList<String>(
                      paramValues.size());
              for (String value : paramValues) {
                  encodedValues.add(SdkHttpUtils.urlEncode(value, false));
              }
              Collections.sort(encodedValues);
              sorted.put(encodedParamName, encodedValues);
  
          }
  
          final StringBuilder result = new StringBuilder();
          for(Map.Entry<String, List<String>> entry : sorted.entrySet()) {
              for(String value : entry.getValue()) {
                  if (result.length() > 0) {
                      result.append("&");
                  }
                  result.append(entry.getKey())
                        .append("=")
                        .append(value);
              }
          }
  
          return result.toString();
      }
docs: Examines the specified query string parameters and returns a
  canonicalized form.
  <p>
  The canonicalized query string is formed by first sorting all the query
  string parameters, then URI encoding both the key and value and then
  joining them, in order, separating key value pairs with an '&'.
  
  @param parameters
  The query string parameters to be canonicalized.
  
  @return A canonicalized form for the specified query string parameters.