class EncodingFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
        if (!response.hasEntity()) {
            return;
        }

        // add Accept-Encoding to Vary header
        List<String> varyHeader = response.getStringHeaders().get(HttpHeaders.VARY);
        if (varyHeader == null || !varyHeader.contains(HttpHeaders.ACCEPT_ENCODING)) {
            response.getHeaders().add(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
        }

        // if Content-Encoding is already set, don't do anything
        if (response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING) != null) {
            return;
        }

        // retrieve the list of accepted encodings
        List<String> acceptEncoding = request.getHeaders().get(HttpHeaders.ACCEPT_ENCODING);

        // if empty, don't do anything
        if (acceptEncoding == null || acceptEncoding.isEmpty()) {
            return;
        }

        // convert encodings from String to Encoding objects
        List<ContentEncoding> encodings = new ArrayList<>();
        for (String input : acceptEncoding) {
            if (input.isEmpty()) {
                continue;
            }
            String[] tokens = input.split(",");
            for (String token : tokens) {
                try {
                    ContentEncoding encoding = ContentEncoding.fromString(token);
                    encodings.add(encoding);
                } catch (ParseException e) {
                    // ignore the encoding that could not parse
                    // but log the exception
                    Logger.getLogger(EncodingFilter.class.getName()).log(Level.WARNING, e.getLocalizedMessage(), e);
                }
            }
        }
        // sort based on quality parameter
        Collections.sort(encodings);
        // make sure IDENTITY_ENCODING is at the end (since it accepted if not explicitly forbidden
        // in the Accept-Content header by assigning q=0
        encodings.add(new ContentEncoding(IDENTITY_ENCODING, -1));

        // get a copy of supported encoding (we'll be modifying this set, hence the copy)
        SortedSet<String> acceptedEncodings = new TreeSet<>(getSupportedEncodings());

        // indicates that we can pick any of the encodings that remained in the acceptedEncodings set
        boolean anyRemaining = false;
        // final resulting value of the Content-Encoding header to be set
        String contentEncoding = null;

        // iterate through the accepted encodings, starting with the highest quality one
        for (ContentEncoding encoding : encodings) {
            if (encoding.q == 0) {
                // ok, we are down at 0 quality
                if ("*".equals(encoding.name)) {
                    // no other encoding is acceptable
                    break;
                }
                // all the 0 quality encodings need to be removed from the accepted ones (these are explicitly
                // forbidden by the client)
                acceptedEncodings.remove(encoding.name);
            } else {
                if ("*".equals(encoding.name)) {
                    // any remaining encoding (after filtering out q=0) will be acceptable
                    anyRemaining = true;
                } else {
                    if (acceptedEncodings.contains(encoding.name)) {
                        // found an acceptable one -> we are done
                        contentEncoding = encoding.name;
                        break;
                    }
                }
            }
        }

        if (contentEncoding == null) {
            // haven't found any explicit acceptable encoding, let's see if we can just pick any of the remaining ones
            // (if there are any left)
            if (anyRemaining && !acceptedEncodings.isEmpty()) {
                contentEncoding = acceptedEncodings.first();
            } else {
                // no acceptable encoding can be sent -> return NOT ACCEPTABLE status code back to the client
                throw new NotAcceptableException();
            }
        }

        // finally set the header - but no need to set for identity encoding
        if (!IDENTITY_ENCODING.equals(contentEncoding)) {
            response.getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, contentEncoding);
        }
    }

}