001    /*
002     * The contents of this file are subject to the terms
003     * of the Common Development and Distribution License
004     * (the "License").  You may not use this file except
005     * in compliance with the License.
006     * 
007     * You can obtain a copy of the license at
008     * http://www.opensource.org/licenses/cddl1.php
009     * See the License for the specific language governing
010     * permissions and limitations under the License.
011     */
012    
013    /*
014     * MediaType.java
015     *
016     * Created on March 22, 2007, 2:35 PM
017     *
018     */
019    
020    package javax.ws.rs.core;
021    
022    import java.util.Collections;
023    import java.util.Comparator;
024    import java.util.Map;
025    import java.util.TreeMap;
026    import javax.ws.rs.ext.RuntimeDelegate;
027    import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
028    
029    /**
030     * An abstraction for a media type. Instances are immutable.
031     * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1 section 3.7</a>
032     */
033    public class MediaType {
034        
035        private String type;
036        private String subtype;
037        private Map<String, String> parameters;
038    
039        /**
040         * Empty immutable map used for all instances without parameters
041         */
042        private static final Map<String, String> emptyMap = Collections.emptyMap();
043        
044        private static final HeaderDelegate<MediaType> delegate = 
045                RuntimeDelegate.getInstance().createHeaderDelegate(MediaType.class);
046    
047        /** The value of a type or subtype wildcard: "*" */
048        public static final String MEDIA_TYPE_WILDCARD = "*";
049        
050        // Common media type constants
051        /** "*&#47;*" */
052        public final static String WILDCARD = "*/*";
053        /** "*&#47;*" */
054        public final static MediaType WILDCARD_TYPE = new MediaType();
055        
056        /** "application/xml" */
057        public final static String APPLICATION_XML = "application/xml";
058        /** "application/xml" */
059        public final static MediaType APPLICATION_XML_TYPE = new MediaType("application","xml");
060        
061        /** "application/atom+xml" */
062        public final static String APPLICATION_ATOM_XML = "application/atom+xml";
063        /** "application/atom+xml" */
064        public final static MediaType APPLICATION_ATOM_XML_TYPE = new MediaType("application","atom+xml");
065        
066        /** "application/xhtml+xml" */
067        public final static String APPLICATION_XHTML_XML = "application/xhtml+xml";
068        /** "application/xhtml+xml" */
069        public final static MediaType APPLICATION_XHTML_XML_TYPE = new MediaType("application","xhtml+xml");
070        
071        /** "application/svg+xml" */
072        public final static String APPLICATION_SVG_XML = "application/svg+xml";
073        /** "application/svg+xml" */
074        public final static MediaType APPLICATION_SVG_XML_TYPE = new MediaType("application","svg+xml");
075        
076        /** "application/json" */
077        public final static String APPLICATION_JSON = "application/json";
078        /** "application/json" */
079        public final static MediaType APPLICATION_JSON_TYPE = new MediaType("application","json");
080    
081        /** "application/x-www-form-urlencoded" */
082        public final static String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
083        /** "application/x-www-form-urlencoded" */
084        public final static MediaType APPLICATION_FORM_URLENCODED_TYPE = new MediaType("application","x-www-form-urlencoded");
085    
086        /** "multipart/form-data" */
087        public final static String MULTIPART_FORM_DATA = "multipart/form-data";
088        /** "multipart/form-data" */
089        public final static MediaType MULTIPART_FORM_DATA_TYPE = new MediaType("multipart","form-data");
090    
091        /** "application/octet-stream" */
092        public final static String APPLICATION_OCTET_STREAM = "application/octet-stream";
093        /** "application/octet-stream" */
094        public final static MediaType APPLICATION_OCTET_STREAM_TYPE = new MediaType("application","octet-stream");
095    
096        /** "text/plain" */
097        public final static String TEXT_PLAIN = "text/plain";
098        /** "text/plain" */
099        public final static MediaType TEXT_PLAIN_TYPE = new MediaType("text","plain");
100    
101        /** "text/xml" */
102        public final static String TEXT_XML = "text/xml";
103        /** "text/xml" */
104        public final static MediaType TEXT_XML_TYPE = new MediaType("text","xml");
105    
106        /** "text/html" */
107        public final static String TEXT_HTML = "text/html";
108        /** "text/html" */
109        public final static MediaType TEXT_HTML_TYPE = new MediaType("text","html");
110    
111        /**
112         * Creates a new instance of MediaType by parsing the supplied string.
113         * @param type the media type string
114         * @return the newly created MediaType
115         * @throws IllegalArgumentException if the supplied string cannot be parsed
116         * or is null
117         */
118        public static MediaType valueOf(String type) throws IllegalArgumentException {
119            return delegate.fromString(type);
120        }
121    
122        /**
123         * Creates a new instance of MediaType with the supplied type, subtype and
124         * parameters. 
125         * @param type the primary type, null is equivalent to 
126         * {@link #MEDIA_TYPE_WILDCARD}.
127         * @param subtype the subtype, null is equivalent to 
128         * {@link #MEDIA_TYPE_WILDCARD}.
129         * @param parameters a map of media type parameters, null is the same as an
130         * empty map.
131         */
132        public MediaType(String type, String subtype, Map<String, String> parameters) {
133            this.type = type==null ? MEDIA_TYPE_WILDCARD : type;
134            this.subtype = subtype==null ? MEDIA_TYPE_WILDCARD : subtype;
135            if (parameters==null) {
136                this.parameters = emptyMap;
137            } else {
138                Map<String, String> map = new TreeMap<String, String>(new Comparator<String>() {
139                    public int compare(String o1, String o2) {
140                        return o1.compareToIgnoreCase(o2);
141                    }        
142                });
143                for (Map.Entry<String, String> e: parameters.entrySet()) {
144                    map.put(e.getKey().toLowerCase(), e.getValue());
145                }
146                this.parameters = Collections.unmodifiableMap(map);
147            }
148        }
149        
150        /**
151         * Creates a new instance of MediaType with the supplied type and subtype.
152         * @param type the primary type, null is equivalent to 
153         * {@link #MEDIA_TYPE_WILDCARD}
154         * @param subtype the subtype, null is equivalent to 
155         * {@link #MEDIA_TYPE_WILDCARD}
156         */
157        public MediaType(String type, String subtype) {
158            this(type,subtype,emptyMap);
159        }
160    
161        /**
162         * Creates a new instance of MediaType, both type and subtype are wildcards.
163         * Consider using the constant {@link #WILDCARD_TYPE} instead.
164         */
165        public MediaType() {
166            this(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD);
167        }
168    
169        /**
170         * Getter for primary type.
171         * @return value of primary type.
172         */
173        public String getType() {
174            return this.type;
175        }
176        
177        /**
178         * Checks if the primary type is a wildcard.
179         * @return true if the primary type is a wildcard
180         */
181        public boolean isWildcardType() {
182            return this.getType().equals(MEDIA_TYPE_WILDCARD);
183        }
184        
185        /**
186         * Getter for subtype.
187         * @return value of subtype.
188         */
189        public String getSubtype() {
190            return this.subtype;
191        }
192    
193        /**
194         * Checks if the subtype is a wildcard
195         * @return true if the subtype is a wildcard  
196         */
197        public boolean isWildcardSubtype() {
198            return this.getSubtype().equals(MEDIA_TYPE_WILDCARD);
199        }
200        
201        /**
202         * Getter for a read-only parameter map. Keys are case-insensitive.
203         * @return an immutable map of parameters.
204         */
205        public Map<String, String> getParameters() {
206            return parameters;
207        }
208        
209        /**
210         * Check if this media type is compatible with another media type. E.g.
211         * image/* is compatible with image/jpeg, image/png, etc. Media type
212         * parameters are ignored. The function is commutative.
213         * @return true if the types are compatible, false otherwise.
214         * @param other the media type to compare with
215         */
216        public boolean isCompatible(MediaType other) {
217            if (other == null)
218                return false;
219            if (type.equals(MEDIA_TYPE_WILDCARD) || other.type.equals(MEDIA_TYPE_WILDCARD))
220                return true;
221            else if (type.equalsIgnoreCase(other.type) && (subtype.equals(MEDIA_TYPE_WILDCARD) || other.subtype.equals(MEDIA_TYPE_WILDCARD)))
222                return true;
223            else
224                return this.type.equalsIgnoreCase(other.type)
225                    && this.subtype.equalsIgnoreCase(other.subtype);
226        }
227        
228        /**
229         * Compares obj to this media type to see if they are the same by comparing
230         * type, subtype and parameters. Note that the case-sensitivity of parameter
231         * values is dependent on the semantics of the parameter name, see
232         * {@link <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1</a>}.
233         * This method assumes that values are case-sensitive.
234         * @param obj the object to compare to
235         * @return true if the two media types are the same, false otherwise.
236         */
237        @Override
238        public boolean equals(Object obj) {
239            if (obj == null)
240                return false;
241            if (!(obj instanceof MediaType))
242                return false;
243            MediaType other = (MediaType)obj;
244            return (this.type.equalsIgnoreCase(other.type)
245                    && this.subtype.equalsIgnoreCase(other.subtype)
246                    && this.parameters.equals(other.parameters));
247        }
248        
249        /**
250         * Generate a hashcode from the type, subtype and parameters.
251         * @return a hashcode
252         */
253        @Override
254        public int hashCode() {
255            return (this.type.toLowerCase()+this.subtype.toLowerCase()).hashCode()+this.parameters.hashCode();
256        }
257        
258        /**
259         * Convert the media type to a string suitable for use as the value of a
260         * corresponding HTTP header.
261         * @return a stringified media type
262         */
263        @Override
264        public String toString() {
265            return delegate.toString(this);
266        }
267    }