/* * Copyright 2007-2009 Medsea Business Solutions S.L. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.medsea.mimeutil; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; /** * This class is used to represent a collection of <code>MimeType</code> objects. * <p> * It uses a {@link LinkedHashSet} as the backing collection and implements all * methods of both the {@link Set} and {@link Collection} interfaces and maintains the list in insertion order. * </p> * <p> * This class is pretty tolerant of the parameter type that can be passed to methods that * take an {@link Object} parameter. These methods can take any of the following types: * <ul> * <li><code>MimeType</code> see {@link MimeType}</li> * <li><code>String</code>. This can be a string representation of a mime type such as text/plain or a * String representing a comma separated list of mime types such as text/plain,application/xml</li> * <li><code>String []</code>. Each element of the array can be a string representation of a mime type or a comma separated * list of mime types. See above.</li> * <li><code>Collection</code>. Each element in the collection can be one of the above types or another Collection.</li> * </ul> * <p> * Also methods taking a Collection as the parameter are able to handle Collections containing elements that are any of the types listed above. * </p> * If an object is passed that is not one of these types the method will throw a MimeException unless the method returns a * boolean in which case it will return false. * </p> * <p> * Note that this implementation is not synchronized. If multiple threads access a set concurrently, and at least one of the threads modifies the set, * it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the set. * If no such object exists, the set should be "wrapped" using the Collections.synchronizedSet method. This is best done at creation time, * to prevent accidental unsynchronized access to the HashSet instance: * <ul> * <li><code>Set s = Collections.synchronizedSet(new MimeTypeHashSet(...));</code></li> * <li><code>Collection c = Collections.synchronizedSet(new MimeTypeHashSet(...));</code></li> * </ul> * @see LinkedHashSet for a description of the way the Iterator works with regard to the fail-fast functionality. * </p> * @author Steven McArdle * */ class MimeTypeHashSet implements Set, Collection { private Set hashSet = new LinkedHashSet(); MimeTypeHashSet() {} /** * Construct a new MimeTypeHashSet from a collection containing elements that can represent mime types. * * @param collection See the introduction to this class for a description of the elements the Collection can contain. */ MimeTypeHashSet(final Collection collection) { addAll(collection); } /** * @see LinkedHashSet#HashSet(int) * @param initialCapacity */ MimeTypeHashSet(final int initialCapacity) { hashSet = new HashSet(initialCapacity); } /** * @see LinkedHashSet#HashSet(int, float) * @param initialCapacity * @param loadFactor */ MimeTypeHashSet(final int initialCapacity, float loadFactor) { hashSet = new HashSet(initialCapacity, loadFactor); } /** * Construct a MimeTypeHashSet from a String object representing a mime type. The String can be a comma separated * list each of which can be a string representation of a mime type. * @param arg0 See the introduction to this class for a description of the String parameter that can be passed. */ MimeTypeHashSet(final String arg0) { add(arg0); } /** * Construct a MimeTypeHashSet from a String [] object representing a mime types. Each string in the array can be a string * representation of a mime type or a comma separated list each of which can be a string representation of a mime type. * @param arg0 See the introduction to this class for a description of the String [] parameter that can be passed. */ MimeTypeHashSet(final String [] arg0) { add(arg0); } /** * Construct a MimeTypeHashSet from a single MimeType * @param mimeType */ MimeTypeHashSet(final MimeType mimeType) { add(mimeType); } /** * This method will add MimeType(s) to the internal HashSet if it does not already contain them. * It is able to take different types of object related to mime types as discussed in the introduction to this class. * <p> * This is a pretty useful override of the HashSet add(Object) method and can be used in the following ways: * </p> * <p> * <ul> * <li>add(String mimeType) examples <code>add("text/plain"); add("text/plain,application/xml");</code></li> * <li>add(String [] mimeTypes) examples <code>add(new String [] {"text/plain", "application/xml"});</code></li> * <li>add(Collection mimeTypes) This delegates to the addAll(Collection) method</li> * <li>add(MimeType)</li> * </ul> * </p> * @param arg0 can be a MimeType, String, String [] or Collection. See the introduction to this class. * @return true if the set did not already contain the specified element. */ public boolean add(final Object arg0) { if(arg0 == null) { // We don't allow null return false; } if((arg0 instanceof MimeType)) { // Add a MimeType if(contains(arg0)) { // We already have an entry so get it and update the specificity updateSpecificity((MimeType)arg0); } MimeUtil.addKnownMimeType((MimeType)arg0); return hashSet.add(arg0); } else if(arg0 instanceof Collection) { // Add a collection return addAll((Collection)arg0); } else if(arg0 instanceof String) { // Add a string representation of a mime type that could be a comma separated list String [] mimeTypes = ((String)arg0).split(","); boolean added = false; for(int i = 0; i < mimeTypes.length; i++) { try { if(add(new MimeType(mimeTypes[i]))) { added = true; } }catch(Exception e) { // Ignore this as it's not a type we can use } } return added; } else if(arg0 instanceof String []) { // Add a String array of mime types each of which can be a comma separated list of mime types boolean added = false; String [] mimeTypes = (String [])arg0; for(int i = 0; i < mimeTypes.length; i++) { String [] parts = mimeTypes[i].split(","); for(int j = 0; j < parts.length; j++) { try { if(add(new MimeType(parts[j]))) { added = true; } }catch(Exception e) { // Ignore this as it's not a type we can use } } } return added; } // Can't add this type return false; } /** * Add a collection of objects to the internal HashSet. See the introduction to this class to see what the Collection can contain. * @param arg0 is a collection of objects each of which should contain or be items that can be used to represent mime types. * Objects that are not recognised as being able to represent a mime type are ignored. * @return true if this collection changed as a result of the call. * @throws NullPointerException */ public boolean addAll(final Collection arg0) throws NullPointerException { if(arg0 == null) { throw new NullPointerException(); } boolean added = false; for(Iterator it = arg0.iterator(); it.hasNext();) { try { if(add(it.next())) { added = true; } }catch(Exception e) { // Ignore this entry as it's not a types that can be turned into MimeTypes } } return added; } /** * @see LinkedHashSet#clear() */ public void clear() { hashSet.clear(); } /** * Checks if this Collection contains the type passed in. See the introduction of this class for a description of the types that can be parsed. * @param an object representing one of the recognised types that can represent mime types. * @return true if this set contains the specified element or elements. */ public boolean contains(final Object o) { if(o instanceof MimeType) { return hashSet.contains(o); } else if(o instanceof Collection) { return containsAll((Collection)o); } else if(o instanceof String) { String [] parts = ((String) o).split(","); for(int i = 0; i < parts.length; i++) { if(!contains(new MimeType(parts[i]))) { return false; } } return true; } else if(o instanceof String []) { String [] mimeTypes = (String [])o; for(int i = 0; i < mimeTypes.length; i++) { String [] parts = mimeTypes[i].split(","); for(int j = 0; j < parts.length; j++) { if(!contains(new MimeType(parts[j]))) { return false; } } } return true; } return false; } /** * Checks that this Collection contains this collection of object that can represent mime types. * See the introduction to this class for a description of these types. * @param arg0 a collection of objects each of which can be a type that can represent a mime type * @ return true if this collection contains all of the elements in the specified collection. * @ throws NullPointerException if the passed in argument in null. */ public boolean containsAll(final Collection arg0) { if(arg0 == null) { throw new NullPointerException(); } for(Iterator it = arg0.iterator(); it.hasNext();){ if(!contains(it.next())) { return false; } } return true; } /** * @see LinkedHashSet#isEmpty() */ public boolean isEmpty() { return hashSet.isEmpty(); } /** * @see LinkedHashSet#iterator() */ public Iterator iterator() { return hashSet.iterator(); } /** * Remove mime types from the collection. The parameter can be any type described in the introduction to this class. * @param o - Object to be removed * @return true if the set was modified. */ public boolean remove(final Object o) { boolean removed = false; if(o == null) { return removed; } if(o instanceof MimeType) { return hashSet.remove(o); }else if(o instanceof String){ String [] parts = ((String)o).split(","); for(int i = 0; i < parts.length; i++) { if(remove(new MimeType(parts[i]))) { removed = true; } } }else if(o instanceof String []) { String [] mimeTypes = (String [])o; for(int i = 0; i < mimeTypes.length; i++) { String [] parts = mimeTypes[i].split(","); for(int j = 0; j < parts.length; j++) { if(remove(new MimeType(parts[j]))) { removed = true; } } } }else if(o instanceof Collection) { return removeAll((Collection)o); } return removed; } /** * Remove all the items in the passed in Collection that can represent a mime type. * See the introduction of this class to see the types of objects the passed in collection can contain. * @return true if the set was modified. * @throws NullPointerException if the Collection passed in is null */ public boolean removeAll(final Collection arg0) { if(arg0 == null) { throw new NullPointerException(); } boolean removed = false; for(Iterator it = ((Collection)arg0).iterator(); it.hasNext();) { if(remove(it.next())) { removed = true; } } return removed; } /** * Keep only the MimeType(s) in this collection that are also present in the passed in collection. * The passed in Collection is normalised into a MimeTypeHashSet before delegating down to the HashSet * retainAll(Collection) method. * @param arg0 - collection of types each of which can represent a mime type. * @ return true if this collection changed as a result of the call. */ public boolean retainAll(final Collection arg0) { if(arg0 == null) { throw new NullPointerException(); } // Make sure our collection is a real collection of MimeType(s) Collection c = new MimeTypeHashSet(arg0); return hashSet.retainAll(c); } /** * @see LinkedHashSet#size() */ public int size() { return hashSet.size(); } /** * @see LinkedHashSet#toArray() */ public Object[] toArray() { return hashSet.toArray(); } /** * @see LinkedHashSet#add(Object) */ public Object[] toArray(final Object[] arg0) { return hashSet.toArray(arg0); } /** * Create a String representation of this Collection as a comma separated list */ public String toString() { StringBuffer buf = new StringBuffer(); for(Iterator it = iterator(); it.hasNext();) { buf.append(((MimeType)it.next()).toString()); if(it.hasNext()) { buf.append(","); } } return buf.toString(); } /** * Compares the specified object with this set for equality. See the introduction of this class for a description of what this parameter can represent * @param o - Object to be compared for equality with this set. * @return true if the specified object is equal to this set. */ public boolean equals(final Object o) { if(o == null) { return false; } Collection c = new MimeTypeHashSet(); c.add(o); return match(c); } private boolean match(final Collection c) { if(this.size() != c.size()) { return false; } MimeType [] mt = (MimeType[])c.toArray(new MimeType [c.size()]); for(int i = 0; i < mt.length; i++) { if(!this.contains(mt[i])) { return false; } } return true; } private void updateSpecificity(final MimeType o) { MimeType mimeType = get(o); int specificity = mimeType.getSpecificity() + o.getSpecificity(); mimeType.setSpecificity(specificity); o.setSpecificity(specificity); } private MimeType get(MimeType mimeType) { for(Iterator it = hashSet.iterator(); it.hasNext();) { MimeType mt = (MimeType)it.next(); if(mt.equals(mimeType)) { return mt; } } return null; } /* * The following functions are extensions to the Collection and Set interfaces * implemented by this class and require the Collection to be cast to a MimeTypeHashSet * before they can be accessed. */ /** * Return a sub collection from this collection containing all MimeType(s) that match the * pattern passed in. The pattern can be any pattern supported by the {@Link Pattern} class. * @param pattern to match against the collection of MimeType(s) * @return Collection of matching MimeType(s) or an empty set if no matches found * @see String#matches(String) for a full description of the regular expression matching */ public Collection matches(String pattern) { Collection c = new MimeTypeHashSet(); for(Iterator it = iterator(); it.hasNext();) { MimeType mimeType = (MimeType)it.next(); if(mimeType.toString().matches(pattern)) { c.add(mimeType); } } return c; } }