Skip to content

MediaType.sortBySpecificityAndQuality throws java.lang.IllegalArgumentException: Comparison method violates its general contract #27488

@samabcde

Description

@samabcde

Affects: spring-web:5.3.9
Original Problem from stackoverflow

Implementation of QUALITY_VALUE_COMPARATOR seems violate general contract of comparator.

The issue can be reproduced by below program.
OS: Window 10
JDK: openjdk version "16" 2021-03-16

import org.springframework.http.MediaType;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class TestMediaTypeSort {
    public static void main(String[] args) {
        demonstrateViolation();
        reproducibleExample();
    }

    private static void reproducibleExample() {
        Map<String, String> mapSize1 = Map.of("a", "b");
        Map<String, String> mapSize2 = Map.of("a", "b", "c", "d");
        Map<String, String> mapSize3 = Map.of("a", "b", "c", "d", "e", "f");
        List<MediaType> mediaTypeList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mediaTypeList.add(new MediaType("c", "a", mapSize1));
            mediaTypeList.add(new MediaType("c", "a", mapSize2));
            mediaTypeList.add(new MediaType("c", "a", mapSize3));
            mediaTypeList.add(new MediaType("b", "a", mapSize1));
            mediaTypeList.add(new MediaType("b", "a", mapSize2));
            mediaTypeList.add(new MediaType("b", "a", mapSize3));
            mediaTypeList.add(new MediaType("b", "a", mapSize2));
        }
        MediaType.sortBySpecificityAndQuality(mediaTypeList);
    }

    private static void demonstrateViolation() {
        Map<String, String> mapSize1 = Map.of("a", "b");
        Map<String, String> mapSize2 = Map.of("a", "b", "c", "d");
        Map<String, String> mapSize3 = Map.of("a", "b", "c", "d", "e", "f");
        MediaType a = new MediaType("c", "a", mapSize1);
        MediaType b = new MediaType("b", "a", mapSize2);
        MediaType c = new MediaType("b", "a", mapSize3);
        System.out.println("Compare a with b:" + QUALITY_VALUE_COMPARATOR.compare(a, b));
        System.out.println("Compare a with c:" + QUALITY_VALUE_COMPARATOR.compare(a, c));
        System.out.println("Compare b with c:" + QUALITY_VALUE_COMPARATOR.compare(b, c));
    }

    // Copied from MediaType.java
    public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
        double quality1 = mediaType1.getQualityValue();
        double quality2 = mediaType2.getQualityValue();
        int qualityComparison = Double.compare(quality2, quality1);
        if (qualityComparison != 0) {
            return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
        } else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) {  // */* < audio/*
            return 1;
        } else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) {  // audio/* > */*
            return -1;
        } else if (!mediaType1.getType().equals(mediaType2.getType())) {  // audio/basic == text/html
            return 0;
        } else {  // mediaType1.getType().equals(mediaType2.getType())
            if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) {  // audio/* < audio/basic
                return 1;
            } else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) {  // audio/basic > audio/*
                return -1;
            } else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) {  // audio/basic == audio/wave
                return 0;
            } else {
                int paramsSize1 = mediaType1.getParameters().size();
                int paramsSize2 = mediaType2.getParameters().size();
                return Integer.compare(paramsSize2, paramsSize1);  // audio/basic;level=1 < audio/basic
            }
        }
    };
}


Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions