View Javadoc

1   /*
2    * Copyright 2007-2009 Medsea Business Solutions S.L.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package eu.medsea.mimeutil;
17  
18  import java.util.Collection;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.LinkedHashSet;
22  import java.util.Set;
23  
24  /**
25   * This class is used to represent a collection of <code>MimeType</code> objects.
26   * <p>
27   * It uses a {@link LinkedHashSet} as the backing collection and implements all
28   * methods of both the {@link Set} and {@link Collection} interfaces and maintains the list in insertion order.
29   * </p>
30   * <p>
31   * This class is pretty tolerant of the parameter type that can be passed to methods that
32   * take an {@link Object} parameter. These methods can take any of the following types:
33   * <ul>
34   * <li><code>MimeType</code> see {@link MimeType}</li>
35   * <li><code>String</code>. This can be a string representation of a mime type such as text/plain or a
36   * String representing a comma separated list of mime types such as text/plain,application/xml</li>
37   * <li><code>String []</code>. Each element of the array can be a string representation of a mime type or a comma separated
38   * list of mime types. See above.</li>
39   * <li><code>Collection</code>. Each element in the collection can be one of the above types or another Collection.</li>
40   * </ul>
41   * <p>
42   * Also methods taking a Collection as the parameter are able to handle Collections containing elements that are any of the types listed above.
43   * </p>
44   * If an object is passed that is not one of these types the method will throw a MimeException unless the method returns a
45   * boolean in which case it will return false.
46   * </p>
47   * <p>
48   * Note that this implementation is not synchronized. If multiple threads access a set concurrently, and at least one of the threads modifies the set,
49   * it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the set.
50   * If no such object exists, the set should be "wrapped" using the Collections.synchronizedSet method. This is best done at creation time,
51   * to prevent accidental unsynchronized access to the HashSet  instance:
52   * <ul>
53   * <li><code>Set s = Collections.synchronizedSet(new MimeTypeHashSet(...));</code></li>
54   * <li><code>Collection c = Collections.synchronizedSet(new MimeTypeHashSet(...));</code></li>
55   * </ul>
56   * @see LinkedHashSet for a description of the way the Iterator works with regard to the fail-fast functionality.
57   * </p>
58   * @author Steven McArdle
59   *
60   */
61  class MimeTypeHashSet implements Set, Collection {
62  
63  	private Set hashSet = new LinkedHashSet();
64  
65  	MimeTypeHashSet() {}
66  
67  	/**
68  	 * Construct a new MimeTypeHashSet from a collection containing elements that can represent mime types.
69  	 *
70  	 * @param collection See the introduction to this class for a description of the elements the Collection can contain.
71  	 */
72  	MimeTypeHashSet(final Collection collection) {
73  		addAll(collection);
74  	}
75  
76  	/**
77  	 * @see LinkedHashSet#HashSet(int)
78  	 * @param initialCapacity
79  	 */
80  	MimeTypeHashSet(final int initialCapacity) {
81  		hashSet = new HashSet(initialCapacity);
82  	}
83  
84  	/**
85  	 * @see LinkedHashSet#HashSet(int, float)
86  	 * @param initialCapacity
87  	 * @param loadFactor
88  	 */
89  	MimeTypeHashSet(final int initialCapacity, float loadFactor) {
90  		hashSet = new HashSet(initialCapacity, loadFactor);
91  	}
92  
93  	/**
94  	 * Construct a MimeTypeHashSet from a String object representing a mime type. The String can be a comma separated
95  	 * list each of which can be a string representation of a mime type.
96  	 * @param arg0 See the introduction to this class for a description of the String parameter that can be passed.
97  	 */
98  	MimeTypeHashSet(final String arg0) {
99  		add(arg0);
100 	}
101 
102 	/**
103 	 * Construct a MimeTypeHashSet from a String [] object representing a mime types. Each string in the array can be a string
104 	 * representation of a mime type or a comma separated list each of which can be a string representation of a mime type.
105 	 * @param arg0 See the introduction to this class for a description of the String [] parameter that can be passed.
106 	 */
107 	MimeTypeHashSet(final String [] arg0) {
108 		add(arg0);
109 	}
110 
111 	/**
112 	 * Construct a MimeTypeHashSet from a single MimeType
113 	 * @param mimeType
114 	 */
115 	MimeTypeHashSet(final MimeType mimeType) {
116 		add(mimeType);
117 	}
118 
119 	/**
120 	 * This method will add MimeType(s) to the internal HashSet if it does not already contain them.
121 	 * It is able to take different types of object related to mime types as discussed in the introduction to this class.
122 	 * <p>
123 	 * This is a pretty useful override of the HashSet add(Object) method and can be used in the following ways:
124 	 * </p>
125 	 * <p>
126 	 * <ul>
127 	 * <li>add(String mimeType) examples <code>add("text/plain"); add("text/plain,application/xml");</code></li>
128 	 * <li>add(String [] mimeTypes) examples <code>add(new String [] {"text/plain", "application/xml"});</code></li>
129 	 * <li>add(Collection mimeTypes) This delegates to the addAll(Collection) method</li>
130 	 * <li>add(MimeType)</li>
131 	 * </ul>
132 	 * </p>
133 	 * @param arg0 can be a MimeType, String, String [] or Collection. See the introduction to this class.
134 	 * @return true if the set did not already contain the specified element.
135 	 */
136 	public boolean add(final Object arg0) {
137 		if(arg0 == null) {
138 			// We don't allow null
139 			return false;
140 		}
141 		if((arg0 instanceof MimeType)) {
142 			// Add a MimeType
143 			if(contains(arg0)) {
144 				// We already have an entry so get it and update the specificity
145 				updateSpecificity((MimeType)arg0);
146 			}
147 			MimeUtil.addKnownMimeType((MimeType)arg0);
148 			return hashSet.add(arg0);
149 
150 		} else if(arg0 instanceof Collection) {
151 			// Add a collection
152 			return addAll((Collection)arg0);
153 		} else if(arg0 instanceof String) {
154 			// Add a string representation of a mime type that could be a comma separated list
155 			String [] mimeTypes = ((String)arg0).split(",");
156 			boolean added = false;
157 			for(int i = 0; i < mimeTypes.length; i++) {
158 				try {
159 					if(add(new MimeType(mimeTypes[i]))) {
160 						added = true;
161 					}
162 				}catch(Exception e) {
163 					// Ignore this as it's not a type we can use
164 				}
165 			}
166 			return added;
167 		} else if(arg0 instanceof String []) {
168 			// Add a String array of mime types each of which can be a comma separated list of mime types
169 			boolean added = false;
170 			String [] mimeTypes = (String [])arg0;
171 			for(int i = 0; i < mimeTypes.length; i++) {
172 				String [] parts = mimeTypes[i].split(",");
173 				for(int j = 0; j < parts.length; j++) {
174 					try {
175 						if(add(new MimeType(parts[j]))) {
176 							added = true;
177 						}
178 					}catch(Exception e) {
179 						// Ignore this as it's not a type we can use
180 					}
181 				}
182 			}
183 			return added;
184 		}
185 		// Can't add this type
186 		return false;
187 	}
188 
189 	/**
190 	 * Add a collection of objects to the internal HashSet. See the introduction to this class to see what the Collection can contain.
191 	 * @param arg0 is a collection of objects each of which should contain or be items that can be used to represent mime types.
192 	 * Objects that are not recognised as being able to represent a mime type are ignored.
193 	 * @return true if this collection changed as a result of the call.
194 	 * @throws NullPointerException
195 	 */
196 	public boolean addAll(final Collection arg0) throws NullPointerException {
197 		if(arg0 == null) {
198 			throw new NullPointerException();
199 		}
200 		boolean added = false;
201 		for(Iterator it = arg0.iterator(); it.hasNext();) {
202 			try {
203 				if(add(it.next())) {
204 					added = true;
205 				}
206 			}catch(Exception e) {
207 				// Ignore this entry as it's not a types that can be turned into MimeTypes
208 			}
209 		}
210 		return added;
211 	}
212 
213 	/**
214 	 * @see LinkedHashSet#clear()
215 	 */
216 	public void clear() {
217 		hashSet.clear();
218 	}
219 
220 	/**
221 	 * 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.
222 	 * @param an object representing one of the recognised types that can represent mime types.
223 	 * @return true if this set contains the specified element or elements.
224 	 */
225 	public boolean contains(final Object o) {
226 		if(o instanceof MimeType) {
227 			return hashSet.contains(o);
228 		} else if(o instanceof Collection) {
229 			return containsAll((Collection)o);
230 		} else if(o instanceof String) {
231 			String [] parts = ((String) o).split(",");
232 			for(int i = 0; i < parts.length; i++) {
233 				if(!contains(new MimeType(parts[i]))) {
234 					return false;
235 				}
236 			}
237 			return true;
238 		} else if(o instanceof String []) {
239 			String [] mimeTypes = (String [])o;
240 			for(int i = 0; i < mimeTypes.length; i++) {
241 				String [] parts = mimeTypes[i].split(",");
242 				for(int j = 0; j < parts.length; j++) {
243 					if(!contains(new MimeType(parts[j]))) {
244 						return false;
245 					}
246 				}
247 			}
248 			return true;
249 		}
250 		return false;
251 	}
252 
253 	/**
254 	 * Checks that this Collection contains this collection of object that can represent mime types.
255 	 * See the introduction to this class for a description of these types.
256 	 * @param arg0 a collection of objects each of which can be a type that can represent a mime type
257 	 * @ return true if this collection contains all of the elements in the specified collection.
258 	 * @ throws NullPointerException if the passed in argument in null.
259 	 */
260 	public boolean containsAll(final Collection arg0) {
261 		if(arg0 == null) {
262 			throw new NullPointerException();
263 		}
264 		for(Iterator it = arg0.iterator(); it.hasNext();){
265 			if(!contains(it.next())) {
266 				return false;
267 			}
268 		}
269 		return true;
270 	}
271 
272 	/**
273 	 * @see LinkedHashSet#isEmpty()
274 	 */
275 	public boolean isEmpty() {
276 		return hashSet.isEmpty();
277 	}
278 
279 	/**
280 	 * @see LinkedHashSet#iterator()
281 	 */
282 	public Iterator iterator() {
283 		return hashSet.iterator();
284 	}
285 
286 	/**
287 	 * Remove mime types from the collection. The parameter can be any type described in the introduction to this class.
288 	 * @param o - Object to be removed
289 	 * @return true if the set was modified.
290 	 */
291 	public boolean remove(final Object o) {
292 		boolean removed = false;
293 		if(o == null) {
294 			return removed;
295 		}
296 		if(o instanceof MimeType) {
297 			return hashSet.remove(o);
298 		}else if(o instanceof String){
299 			String [] parts = ((String)o).split(",");
300 			for(int i = 0; i < parts.length; i++) {
301 				if(remove(new MimeType(parts[i]))) {
302 					removed = true;
303 				}
304 			}
305 		}else if(o instanceof String []) {
306 			String [] mimeTypes = (String [])o;
307 			for(int i = 0; i < mimeTypes.length; i++) {
308 				String [] parts = mimeTypes[i].split(",");
309 				for(int j = 0; j < parts.length; j++) {
310 					if(remove(new MimeType(parts[j]))) {
311 						removed = true;
312 					}
313 				}
314 			}
315 		}else if(o instanceof Collection) {
316 			return removeAll((Collection)o);
317 		}
318 		return removed;
319 	}
320 
321 	/**
322 	 * Remove all the items in the passed in Collection that can represent a mime type.
323 	 * See the introduction of this class to see the types of objects the passed in collection can contain.
324 	 * @return true if the set was modified.
325 	 * @throws NullPointerException if the Collection passed in is null
326 	 */
327 	public boolean removeAll(final Collection arg0) {
328 		if(arg0 == null) {
329 			throw new NullPointerException();
330 		}
331 		boolean removed = false;
332 		for(Iterator it = ((Collection)arg0).iterator(); it.hasNext();) {
333 			if(remove(it.next())) {
334 				removed = true;
335 			}
336 		}
337 		return removed;
338 	}
339 
340 	/**
341 	 * Keep only the MimeType(s) in this collection that are also present in the passed in collection.
342 	 * The passed in Collection is normalised into a MimeTypeHashSet before delegating down to the HashSet
343 	 * retainAll(Collection) method.
344 	 * @param arg0 - collection of types each of which can represent a mime type.
345 	 * @ return true if this collection changed as a result of the call.
346 	 */
347 	public boolean retainAll(final Collection arg0) {
348 		if(arg0 == null) {
349 			throw new NullPointerException();
350 		}
351 		// Make sure our collection is a real collection of MimeType(s)
352 		Collection c = new MimeTypeHashSet(arg0);
353 		return hashSet.retainAll(c);
354 	}
355 
356 	/**
357 	 * @see LinkedHashSet#size()
358 	 */
359 	public int size() {
360 		return hashSet.size();
361 	}
362 
363 	/**
364 	 * @see LinkedHashSet#toArray()
365 	 */
366 	public Object[] toArray() {
367 		return hashSet.toArray();
368 	}
369 
370 	/**
371 	 * @see LinkedHashSet#add(Object)
372 	 */
373 	public Object[] toArray(final Object[] arg0) {
374 		return hashSet.toArray(arg0);
375 	}
376 
377 	/**
378 	 * Create a String representation of this Collection as a comma separated list
379 	 */
380 	public String toString() {
381 		StringBuffer buf = new StringBuffer();
382 		for(Iterator it = iterator(); it.hasNext();) {
383 			buf.append(((MimeType)it.next()).toString());
384 			if(it.hasNext()) {
385 				buf.append(",");
386 			}
387 		}
388 		return buf.toString();
389 	}
390 
391 	/**
392 	 * Compares the specified object with this set for equality. See the introduction of this class for a description of what this parameter can represent
393 	 * @param o - Object to be compared for equality with this set.
394 	 * @return true if the specified object is equal to this set.
395 	 */
396 	public boolean equals(final Object o) {
397 		if(o == null) {
398 			return false;
399 		}
400 		Collection c = new MimeTypeHashSet();
401 		c.add(o);
402 		return match(c);
403 	}
404 
405 	private boolean match(final Collection c) {
406 		if(this.size() != c.size()) {
407 			return false;
408 		}
409 		MimeType [] mt = (MimeType[])c.toArray(new MimeType [c.size()]);
410 
411 		for(int i = 0; i < mt.length; i++) {
412 			if(!this.contains(mt[i])) {
413 				return false;
414 			}
415 		}
416 		return true;
417 	}
418 
419 	private void updateSpecificity(final MimeType o) {
420 		MimeType mimeType = get(o);
421 		int specificity = mimeType.getSpecificity() + o.getSpecificity();
422 		mimeType.setSpecificity(specificity);
423 		o.setSpecificity(specificity);
424 	}
425 
426 	private MimeType get(MimeType mimeType) {
427 		for(Iterator it = hashSet.iterator(); it.hasNext();) {
428 			MimeType mt = (MimeType)it.next();
429 			if(mt.equals(mimeType)) {
430 				return mt;
431 			}
432 		}
433 		return null;
434 	}
435 
436 	/*
437 	 * The following functions are extensions to the Collection and Set interfaces
438 	 * implemented by this class and require the Collection to be cast to a MimeTypeHashSet
439 	 * before they can be accessed.
440 	 */
441 
442 
443 	/**
444 	 * Return a sub collection from this collection containing all MimeType(s) that match the
445 	 * pattern passed in. The pattern can be any pattern supported by the {@Link Pattern} class.
446 	 * @param pattern to match against the collection of MimeType(s)
447 	 * @return Collection of matching MimeType(s) or an empty set if no matches found
448 	 * @see String#matches(String) for a full description of the regular expression matching
449 	 */
450 	public Collection matches(String pattern) {
451 		Collection c = new MimeTypeHashSet();
452 
453 		for(Iterator it = iterator(); it.hasNext();) {
454 			MimeType mimeType = (MimeType)it.next();
455 			if(mimeType.toString().matches(pattern)) {
456 				c.add(mimeType);
457 			}
458 		}
459 		return c;
460 	}
461 }