docs: public String toNormalizedQueryString(FhirContext theCtx) {
  		StringBuilder b = new StringBuilder();
  
  		ArrayList<String> keys = new ArrayList<>(keySet());
  		Collections.sort(keys);
  		for (String nextKey : keys) {
  
  			List<List<IQueryParameterType>> nextValuesAndsIn = get(nextKey);
  			List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>();
  
  			for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
  
  				List<IQueryParameterType> nextValuesOrsOut = new ArrayList<>();
  				for (IQueryParameterType nextValueOrIn : nextValuesAndIn) {
  					if (nextValueOrIn.getMissing() != null || isNotBlank(nextValueOrIn.getValueAsQueryToken(theCtx))) {
  						nextValuesOrsOut.add(nextValueOrIn);
  					}
  				}
  
  				nextValuesOrsOut.sort(new QueryParameterTypeComparator(theCtx));
  
  				if (nextValuesOrsOut.size() > 0) {
  					nextValuesAndsOut.add(nextValuesOrsOut);
  				}
  
  			} // for AND
  
  			nextValuesAndsOut.sort(new QueryParameterOrComparator(theCtx));
  
  			for (List<IQueryParameterType> nextValuesAnd : nextValuesAndsOut) {
  				addUrlParamSeparator(b);
  				IQueryParameterType firstValue = nextValuesAnd.get(0);
  				b.append(UrlUtil.escapeUrlParam(nextKey));
  
  				if (nextKey.equals(Constants.PARAM_HAS)) {
  					b.append(':');
  				}
  
  				if (firstValue.getMissing() != null) {
  					b.append(Constants.PARAMQUALIFIER_MISSING);
  					b.append('=');
  					if (firstValue.getMissing()) {
  						b.append(Constants.PARAMQUALIFIER_MISSING_TRUE);
  					} else {
  						b.append(Constants.PARAMQUALIFIER_MISSING_FALSE);
  					}
  					continue;
  				}
  
  				if (isNotBlank(firstValue.getQueryParameterQualifier())) {
  					b.append(firstValue.getQueryParameterQualifier());
  				}
  
  				b.append('=');
  
  				for (int i = 0; i < nextValuesAnd.size(); i++) {
  					IQueryParameterType nextValueOr = nextValuesAnd.get(i);
  					if (i > 0) {
  						b.append(',');
  					}
  					String valueAsQueryToken = nextValueOr.getValueAsQueryToken(theCtx);
  					b.append(UrlUtil.escapeUrlParam(valueAsQueryToken));
  				}
  			}
  
  		} // for keys
  
  		SortSpec sort = getSort();
  		boolean first = true;
  		while (sort != null) {
  
  			if (isNotBlank(sort.getParamName())) {
  				if (first) {
  					addUrlParamSeparator(b);
  					b.append(Constants.PARAM_SORT);
  					b.append('=');
  					first = false;
  				} else {
  					b.append(',');
  				}
  				if (sort.getOrder() == SortOrderEnum.DESC) {
  					b.append('-');
  				}
  				b.append(sort.getParamName());
  			}
  
  			Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen
  			sort = sort.getChain();
  		}
  
  		addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes());
  		addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes());
  
  		if (getLastUpdated() != null) {
  			DateParam lb = getLastUpdated().getLowerBound();
  			addLastUpdateParam(b, lb);
  			DateParam ub = getLastUpdated().getUpperBound();
  			addLastUpdateParam(b, ub);
  		}
  
  		if (getCount() != null) {
  			addUrlParamSeparator(b);
  			b.append(Constants.PARAM_COUNT);
  			b.append('=');
  			b.append(getCount());
  		}
  
  		// Summary
  		if (getSummaryMode() != null) {
  			addUrlParamSeparator(b);
  			b.append(Constants.PARAM_SUMMARY);
  			b.append('=');
  			b.append(getSummaryMode().getCode());
  		}
  
  		if (b.length() == 0) {
  			b.append('?');
  		}
  
  		return b.toString();
  	}