001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.types; 020 021import java.text.ParseException; 022import java.util.HashMap; 023import java.util.Map; 024 025public class MediaRange extends MediaBase implements Comparable<MediaRange> { 026 027 public static final MediaRange ALL_MEDIA = new MediaRange("*", "*"); 028 029 /** 030 * Create a new object with the given type and subtype. 031 * 032 * @param type the top-level type 033 * @param subtype the subtype 034 */ 035 public MediaRange(String type, String subtype) { 036 super(new MediaTypePair(type, subtype)); 037 } 038 039 /** 040 * Create a new object with the given type, subtype and parameters. 041 * 042 * @param type the top-level type 043 * @param subtype the subtype 044 * @param parameters the parameters 045 */ 046 public MediaRange(String type, String subtype, 047 Map<String, String> parameters) { 048 super(new MediaTypePair(type, subtype), parameters); 049 } 050 051 /** 052 * Create a new object with the given type and parameters. 053 * 054 * @param type the type 055 * @param parameters the parameters 056 */ 057 public MediaRange(MediaTypePair type, Map<String, String> parameters) { 058 super(type, parameters); 059 } 060 061 /* (non-Javadoc) 062 * @see java.lang.Comparable#compareTo(java.lang.Object) 063 */ 064 @Override 065 public int compareTo(MediaRange other) { 066 float myQuality = 1; 067 String param = parameter("q"); 068 if (param != null) { 069 myQuality = Float.parseFloat(param); 070 } 071 float otherQuality = 1; 072 param = other.parameter("q"); 073 if (param != null) { 074 otherQuality = Float.parseFloat(param); 075 } 076 if (myQuality != otherQuality) { 077 return -(int)Math.signum(myQuality - otherQuality); 078 } 079 080 // Same or no quality, look for wildcards 081 if (!subtype().equals("*") && other.subtype().equals("*")) { 082 return -1; 083 } 084 if (subtype().equals("*") && !other.subtype().equals("*")) { 085 return 1; 086 } 087 if (!topLevelType().equals("*") 088 && other.topLevelType().equals("*")) { 089 return -1; 090 } 091 if (topLevelType().equals("*") 092 && !other.topLevelType().equals("*")) { 093 return 1; 094 } 095 096 // No wildcards or same type, look for number of parameters 097 return countParameters(other)- countParameters(this); 098 } 099 100 private static int countParameters(ParameterizedValue<?> value) { 101 return (int)value.parameters().keySet().stream() 102 .filter(k -> !"q".equals(k)).count(); 103 } 104 105 /** 106 * Checks if the given media type falls within this range. 107 * 108 * @param type the type to check 109 * @return the result 110 */ 111 public boolean matches(MediaType type) { 112 if (!("*".equals(topLevelType()) 113 || topLevelType().equals(type.topLevelType()))) { 114 // Top level is neither * nor match 115 return false; 116 } 117 if (!("*".equals(subtype()) 118 || subtype().equals(type.subtype()))) { 119 // Subtype is neither * nor match 120 return false; 121 } 122 for (Map.Entry<String,String> e: parameters().entrySet()) { 123 if ("q".equals(e.getKey())) { 124 continue; 125 } 126 if (!type.parameters().containsKey(e.getKey())) { 127 return false; 128 } 129 if (!type.parameter(e.getKey()).equals(e.getValue())) { 130 return false; 131 } 132 } 133 return true; 134 } 135 136 /** 137 * Creates a new builder for a media type. 138 * 139 * @return the builder 140 */ 141 @SuppressWarnings("unchecked") 142 public static Builder builder() { 143 return new Builder(); 144 } 145 146 /** 147 * A builder for the (immutable) parameterized type. 148 */ 149 public static class Builder 150 extends ParameterizedValue.Builder<MediaRange, MediaTypePair> { 151 152 private Builder() { 153 super(new MediaRange("*", "*", new HashMap<>())); 154 } 155 156 /** 157 * Sets the media range. 158 * 159 * @param topLevelType the top level type 160 * @param subtype the subtype 161 * @return the builder for easy chaining 162 */ 163 public Builder setType(String topLevelType, String subtype) { 164 setValue(new MediaTypePair(topLevelType, subtype)); 165 return this; 166 } 167 } 168 169 public static class MediaRangeConverter 170 extends ParamValueConverterBase<MediaRange, MediaTypePair> { 171 172 public MediaRangeConverter() { 173 super(new MediaTypePairConverter() { 174 175 /** 176 * Work around buggy clients, notably `HttpUrlConnection` (see 177 * [JDK-8163921](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8163921)) 178 */ 179 @Override 180 public MediaTypePair fromFieldValue(String text) 181 throws ParseException { 182 if ("*".equals(text)) { 183 return MediaTypePair.ALL_MEDIA; 184 } 185 return super.fromFieldValue(text); 186 } 187 }, 188 Converters.UNQUOTE_ONLY, MediaRange::new); 189 } 190 } 191 192}