StringBuilder addCanonicalHeaders(
     SortedMap<String, String> sortedFormattedHeaders, StringBuilder builder) {
   for (Map.Entry<String, String> entry : sortedFormattedHeaders.entrySet()) {
     builder.append(entry.getKey()).append(':').append(entry.getValue()).append('\n');
   }
   return builder;
 }
 StringBuilder addCanonicalQueryString(String queryString, StringBuilder builder) {
   int startingLength = builder.length();
   SortedMap<String, String> encodedParams = new TreeMap<>();
   for (String queryParam : queryString.split("&")) {
     if (!queryParam.isEmpty()) {
       String[] parts = queryParam.split("=", 2);
       encodedParams.put(encodeQueryStringValue(parts[0]), encodeQueryStringValue(parts[1]));
     }
   }
   for (Map.Entry<String, String> entry : encodedParams.entrySet()) {
     if (builder.length() > startingLength) {
       builder.append('&');
     }
     builder.append(entry.getKey()).append('=').append(entry.getValue());
   }
   return builder;
 }