/*
* 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;
}
}