@Override
  public ContactInfo readValue() throws IOException {
    ContactInfo result = null;

    skipSpaces();
    if (peek() != -1) {
      result = new ContactInfo();
      if (peek() == '"') {
        result.setDisplayName(readQuotedString());
        skipSpaces();
        result.setReference(new Reference(readReference()));
      } else if (peek() == '<') {
        result.setReference(new Reference(readReference()));
      } else if (HeaderUtils.isTokenChar(peek())) {
        // Read value until end or value or parameter separator
        StringBuilder sb = null;
        int next = read();

        while ((next != -1) && !isComma(next) && !isSemiColon(next)) {
          if (sb == null) {
            sb = new StringBuilder();
          }

          sb.append((char) next);
          next = read();
        }

        // Remove trailing spaces
        if (sb != null) {
          for (int i = sb.length() - 1; (i >= 0) && isLinearWhiteSpace(sb.charAt(i)); i--) {
            sb.deleteCharAt(i);
          }
        }

        // Unread the separator
        if (isComma(next) || isSemiColon(next)) {
          unread();
        }

        // The last token is the reference
        int index = sb.lastIndexOf(" ");
        if (index != -1) {
          if (sb.charAt(index + 1) == '<') {
            if (sb.charAt(sb.length() - 1) == '>') {
              result.setReference(new Reference(sb.substring(index + 2, sb.length() - 1)));
            } else {
              throw new IOException("Unexpected end of reference. Please check your value");
            }
          }
          result.setDisplayName(sb.substring(0, index).trim());
        } else {
          result.setReference(new Reference(sb.toString()));
        }
      }
    }

    // Read address parameters.
    if (skipParameterSeparator()) {
      Parameter param = readParameter();

      while (param != null) {
        if ("q".equals(param.getName())) {
          result.setQuality(PreferenceReader.readQuality(param.getValue()));
        } else if ("expires".equals(param.getName())) {
          result.setExpires(param.getValue());
        } else {
          addParameter(result, param);
        }

        if (skipParameterSeparator()) {
          param = readParameter();
        } else {
          param = null;
        }
      }
    }

    return result;
  }
  /**
   * Returns the client-specific information.
   *
   * @return The client-specific information.
   */
  @Override
  public ClientInfo getClientInfo() {
    ClientInfo result = super.getClientInfo();

    if (!this.clientAdded) {
      if (getHeaders() != null) {
        // Extract the header values
        String acceptMediaType = getHeaders().getValues(HeaderConstants.HEADER_ACCEPT);
        String acceptCharset = getHeaders().getValues(HeaderConstants.HEADER_ACCEPT_CHARSET);
        String acceptEncoding = getHeaders().getValues(HeaderConstants.HEADER_ACCEPT_ENCODING);
        String acceptLanguage = getHeaders().getValues(HeaderConstants.HEADER_ACCEPT_LANGUAGE);
        String acceptPatch = getHeaders().getValues(HeaderConstants.HEADER_ACCEPT_PATCH);
        String expect = getHeaders().getValues(HeaderConstants.HEADER_EXPECT);

        // Parse the headers and update the call preferences

        // Parse the Accept* headers. If an error occurs during the
        // parsing
        // of each header, the error is traced and we keep on with the
        // other
        // headers.
        try {
          PreferenceReader.addCharacterSets(acceptCharset, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        try {
          PreferenceReader.addEncodings(acceptEncoding, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        try {
          PreferenceReader.addLanguages(acceptLanguage, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        try {
          PreferenceReader.addMediaTypes(acceptMediaType, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        try {
          PreferenceReader.addPatches(acceptPatch, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        try {
          ExpectationReader.addValues(expect, result);
        } catch (Exception e) {
          this.context.getLogger().log(Level.INFO, e.getMessage());
        }

        // Set other properties
        result.setAgent(getHeaders().getValues(HeaderConstants.HEADER_USER_AGENT));
        result.setFrom(getHeaders().getFirstValue(HeaderConstants.HEADER_FROM, true));
        result.setAddress(getConnection().getAddress());
        result.setPort(getConnection().getPort());

        if (userPrincipal != null) {
          result.getPrincipals().add(userPrincipal);
        }

        if (this.context != null) {
          // Special handling for the non standard but common
          // "X-Forwarded-For" header.
          final boolean useForwardedForHeader =
              Boolean.parseBoolean(
                  this.context.getParameters().getFirstValue("useForwardedForHeader", false));
          if (useForwardedForHeader) {
            // Lookup the "X-Forwarded-For" header supported by
            // popular
            // proxies and caches.
            final String header = getHeaders().getValues(HeaderConstants.HEADER_X_FORWARDED_FOR);
            if (header != null) {
              final String[] addresses = header.split(",");
              for (int i = 0; i < addresses.length; i++) {
                String address = addresses[i].trim();
                result.getForwardedAddresses().add(address);
              }
            }
          }
        }
      }

      this.clientAdded = true;
    }

    return result;
  }