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 }