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 }