1 // Copyright 2003-2005 Arthur van Hoff Rick Blair 2 // Licensed under Apache License version 2.0 3 // Original license LGPL 4 5 package javax.jmdns.impl; 6 7 import java.util.AbstractMap; 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.Collections; 11 import java.util.HashSet; 12 import java.util.Iterator; 13 import java.util.List; 14 import java.util.Map; 15 import java.util.Set; 16 17 import javax.jmdns.impl.constants.DNSRecordClass; 18 import javax.jmdns.impl.constants.DNSRecordType; 19 20 /** 21 * A table of DNS entries. This is a map table which can handle multiple entries with the same name. 22 * <p/> 23 * Storing multiple entries with the same name is implemented using a linked list. This is hidden from the user and can change in later implementation. 24 * <p/> 25 * Here's how to iterate over all entries: 26 * 27 * <pre> 28 * for (Iterator i=dnscache.allValues().iterator(); i.hasNext(); ) { 29 * DNSEntry entry = i.next(); 30 * ...do something with entry... 31 * } 32 * </pre> 33 * <p/> 34 * And here's how to iterate over all entries having a given name: 35 * 36 * <pre> 37 * for (Iterator i=dnscache.getDNSEntryList(name).iterator(); i.hasNext(); ) { 38 * DNSEntry entry = i.next(); 39 * ...do something with entry... 40 * } 41 * </pre> 42 * 43 * @author Arthur van Hoff, Werner Randelshofer, Rick Blair, Pierre Frisch 44 */ 45 public class DNSCache extends AbstractMap<String, List<? extends DNSEntry>> { 46 47 // private static Logger logger = Logger.getLogger(DNSCache.class.getName()); 48 49 private transient Set<Map.Entry<String, List<? extends DNSEntry>>> _entrySet = null; 50 51 /** 52 * 53 */ 54 public static final DNSCache EmptyCache = new _EmptyCache(); 55 56 static final class _EmptyCache extends DNSCache { 57 58 /** 59 * {@inheritDoc} 60 */ 61 @Override 62 public int size() { 63 return 0; 64 } 65 66 /** 67 * {@inheritDoc} 68 */ 69 @Override 70 public boolean isEmpty() { 71 return true; 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 @Override 78 public boolean containsKey(Object key) { 79 return false; 80 } 81 82 /** 83 * {@inheritDoc} 84 */ 85 @Override 86 public boolean containsValue(Object value) { 87 return false; 88 } 89 90 /** 91 * {@inheritDoc} 92 */ 93 @Override 94 public List<DNSEntry> get(Object key) { 95 return null; 96 } 97 98 /** 99 * {@inheritDoc} 100 */ 101 @Override 102 public Set<String> keySet() { 103 return Collections.emptySet(); 104 } 105 106 /** 107 * {@inheritDoc} 108 */ 109 @Override 110 public Collection<List<? extends DNSEntry>> values() { 111 return Collections.emptySet(); 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override 118 public Set<Map.Entry<String, List<? extends DNSEntry>>> entrySet() { 119 return Collections.emptySet(); 120 } 121 122 /** 123 * {@inheritDoc} 124 */ 125 @Override 126 public boolean equals(Object o) { 127 return (o instanceof Map) && ((Map<?, ?>) o).size() == 0; 128 } 129 130 /** 131 * {@inheritDoc} 132 */ 133 @Override 134 public List<? extends DNSEntry> put(String key, List<? extends DNSEntry> value) { 135 return null; 136 } 137 138 /** 139 * {@inheritDoc} 140 */ 141 @Override 142 public int hashCode() { 143 return 0; 144 } 145 146 } 147 148 /** 149 * 150 */ 151 protected static class _CacheEntry extends Object implements Map.Entry<String, List<? extends DNSEntry>> { 152 153 private List<? extends DNSEntry> _value; 154 155 private String _key; 156 157 /** 158 * @param key 159 * @param value 160 */ 161 protected _CacheEntry(String key, List<? extends DNSEntry> value) { 162 super(); 163 _key = (key != null ? key.trim().toLowerCase() : null); 164 _value = value; 165 } 166 167 /** 168 * @param entry 169 */ 170 protected _CacheEntry(Map.Entry<String, List<? extends DNSEntry>> entry) { 171 super(); 172 if (entry instanceof _CacheEntry) { 173 _key = ((_CacheEntry) entry).getKey(); 174 _value = ((_CacheEntry) entry).getValue(); 175 } 176 } 177 178 /** 179 * {@inheritDoc} 180 */ 181 @Override 182 public String getKey() { 183 return (_key != null ? _key : ""); 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 public List<? extends DNSEntry> getValue() { 191 return _value; 192 } 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override 198 public List<? extends DNSEntry> setValue(List<? extends DNSEntry> value) { 199 List<? extends DNSEntry> oldValue = _value; 200 _value = value; 201 return oldValue; 202 } 203 204 /** 205 * Returns <tt>true</tt> if this list contains no elements. 206 * 207 * @return <tt>true</tt> if this list contains no elements 208 */ 209 public boolean isEmpty() { 210 return this.getValue().isEmpty(); 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 217 public boolean equals(Object entry) { 218 if (!(entry instanceof Map.Entry)) { 219 return false; 220 } 221 return this.getKey().equals(((Map.Entry<?, ?>) entry).getKey()) && this.getValue().equals(((Map.Entry<?, ?>) entry).getValue()); 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public int hashCode() { 229 return (_key == null ? 0 : _key.hashCode()); 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public synchronized String toString() { 237 StringBuffer aLog = new StringBuffer(200); 238 aLog.append("\n\t\tname '"); 239 aLog.append(_key); 240 aLog.append("' "); 241 if ((_value != null) && (!_value.isEmpty())) { 242 for (DNSEntry entry : _value) { 243 aLog.append("\n\t\t\t"); 244 aLog.append(entry.toString()); 245 } 246 } else { 247 aLog.append(" no entries"); 248 } 249 return aLog.toString(); 250 } 251 } 252 253 /** 254 * 255 */ 256 public DNSCache() { 257 this(1024); 258 } 259 260 /** 261 * @param map 262 */ 263 public DNSCache(DNSCache map) { 264 this(map != null ? map.size() : 1024); 265 if (map != null) { 266 this.putAll(map); 267 } 268 } 269 270 /** 271 * Create a table with a given initial size. 272 * 273 * @param initialCapacity 274 */ 275 public DNSCache(int initialCapacity) { 276 super(); 277 _entrySet = new HashSet<Map.Entry<String, List<? extends DNSEntry>>>(initialCapacity); 278 } 279 280 // ==================================================================== 281 // Map 282 283 /* 284 * (non-Javadoc) 285 * @see java.util.AbstractMap#entrySet() 286 */ 287 @Override 288 public Set<Map.Entry<String, List<? extends DNSEntry>>> entrySet() { 289 if (_entrySet == null) { 290 _entrySet = new HashSet<Map.Entry<String, List<? extends DNSEntry>>>(); 291 } 292 return _entrySet; 293 } 294 295 /** 296 * @param key 297 * @return map entry for the key 298 */ 299 protected Map.Entry<String, List<? extends DNSEntry>> getEntry(String key) { 300 String stringKey = (key != null ? key.trim().toLowerCase() : null); 301 for (Map.Entry<String, List<? extends DNSEntry>> entry : this.entrySet()) { 302 if (stringKey != null) { 303 if (stringKey.equals(entry.getKey())) { 304 return entry; 305 } 306 } else { 307 if (entry.getKey() == null) { 308 return entry; 309 } 310 } 311 } 312 return null; 313 } 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override 319 public List<? extends DNSEntry> put(String key, List<? extends DNSEntry> value) { 320 synchronized (this) { 321 List<? extends DNSEntry> oldValue = null; 322 Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(key); 323 if (oldEntry != null) { 324 oldValue = oldEntry.setValue(value); 325 } else { 326 this.entrySet().add(new _CacheEntry(key, value)); 327 } 328 return oldValue; 329 } 330 } 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override 336 protected Object clone() throws CloneNotSupportedException { 337 return new DNSCache(this); 338 } 339 340 // ==================================================================== 341 342 /** 343 * Returns all entries in the cache 344 * 345 * @return all entries in the cache 346 */ 347 public synchronized Collection<DNSEntry> allValues() { 348 List<DNSEntry> allValues = new ArrayList<DNSEntry>(); 349 for (List<? extends DNSEntry> entry : this.values()) { 350 if (entry != null) { 351 allValues.addAll(entry); 352 } 353 } 354 return allValues; 355 } 356 357 /** 358 * Iterate only over items with matching name. Returns an list of DNSEntry or null. To retrieve all entries, one must iterate over this linked list. 359 * 360 * @param name 361 * @return list of DNSEntries 362 */ 363 public synchronized Collection<? extends DNSEntry> getDNSEntryList(String name) { 364 Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); 365 if (entryList != null) { 366 entryList = new ArrayList<DNSEntry>(entryList); 367 } else { 368 entryList = Collections.emptyList(); 369 } 370 return entryList; 371 } 372 373 private Collection<? extends DNSEntry> _getDNSEntryList(String name) { 374 return this.get(name != null ? name.toLowerCase() : null); 375 } 376 377 /** 378 * Get a matching DNS entry from the table (using isSameEntry). Returns the entry that was found. 379 * 380 * @param dnsEntry 381 * @return DNSEntry 382 */ 383 public synchronized DNSEntry getDNSEntry(DNSEntry dnsEntry) { 384 DNSEntry result = null; 385 if (dnsEntry != null) { 386 Collection<? extends DNSEntry> entryList = this._getDNSEntryList(dnsEntry.getKey()); 387 if (entryList != null) { 388 for (DNSEntry testDNSEntry : entryList) { 389 if (testDNSEntry.isSameEntry(dnsEntry)) { 390 result = testDNSEntry; 391 break; 392 } 393 } 394 } 395 } 396 return result; 397 } 398 399 /** 400 * Get a matching DNS entry from the table. 401 * 402 * @param name 403 * @param type 404 * @param recordClass 405 * @return DNSEntry 406 */ 407 public synchronized DNSEntry getDNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass) { 408 DNSEntry result = null; 409 Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); 410 if (entryList != null) { 411 for (DNSEntry testDNSEntry : entryList) { 412 if (testDNSEntry.getRecordType().equals(type) && ((DNSRecordClass.CLASS_ANY == recordClass) || testDNSEntry.getRecordClass().equals(recordClass))) { 413 result = testDNSEntry; 414 break; 415 } 416 } 417 } 418 return result; 419 } 420 421 /** 422 * Get all matching DNS entries from the table. 423 * 424 * @param name 425 * @param type 426 * @param recordClass 427 * @return list of entries 428 */ 429 public synchronized Collection<? extends DNSEntry> getDNSEntryList(String name, DNSRecordType type, DNSRecordClass recordClass) { 430 Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); 431 if (entryList != null) { 432 entryList = new ArrayList<DNSEntry>(entryList); 433 for (Iterator<? extends DNSEntry> i = entryList.iterator(); i.hasNext();) { 434 DNSEntry testDNSEntry = i.next(); 435 if (!testDNSEntry.getRecordType().equals(type) || ((DNSRecordClass.CLASS_ANY != recordClass) && !testDNSEntry.getRecordClass().equals(recordClass))) { 436 i.remove(); 437 } 438 } 439 } else { 440 entryList = Collections.emptyList(); 441 } 442 return entryList; 443 } 444 445 /** 446 * Adds an entry to the table. 447 * 448 * @param dnsEntry 449 * @return true if the entry was added 450 */ 451 public synchronized boolean addDNSEntry(final DNSEntry dnsEntry) { 452 boolean result = false; 453 if (dnsEntry != null) { 454 Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(dnsEntry.getKey()); 455 456 List<DNSEntry> aNewValue = null; 457 if (oldEntry != null) { 458 aNewValue = new ArrayList<DNSEntry>(oldEntry.getValue()); 459 } else { 460 aNewValue = new ArrayList<DNSEntry>(); 461 } 462 aNewValue.add(dnsEntry); 463 464 if (oldEntry != null) { 465 oldEntry.setValue(aNewValue); 466 } else { 467 this.entrySet().add(new _CacheEntry(dnsEntry.getKey(), aNewValue)); 468 } 469 // This is probably not very informative 470 result = true; 471 } 472 return result; 473 } 474 475 /** 476 * Removes a specific entry from the table. Returns true if the entry was found. 477 * 478 * @param dnsEntry 479 * @return true if the entry was removed 480 */ 481 public synchronized boolean removeDNSEntry(DNSEntry dnsEntry) { 482 boolean result = false; 483 if (dnsEntry != null) { 484 Map.Entry<String, List<? extends DNSEntry>> existingEntry = this.getEntry(dnsEntry.getKey()); 485 if (existingEntry != null) { 486 result = existingEntry.getValue().remove(dnsEntry); 487 // If we just removed the last one we need to get rid of the entry 488 if (existingEntry.getValue().isEmpty()) { 489 this.entrySet().remove(existingEntry); 490 } 491 } 492 } 493 return result; 494 } 495 496 /** 497 * Replace an existing entry by a new one.<br/> 498 * <b>Note:</b> the 2 entries must have the same key. 499 * 500 * @param newDNSEntry 501 * @param existingDNSEntry 502 * @return <code>true</code> if the entry has been replace, <code>false</code> otherwise. 503 */ 504 public synchronized boolean replaceDNSEntry(DNSEntry newDNSEntry, DNSEntry existingDNSEntry) { 505 boolean result = false; 506 if ((newDNSEntry != null) && (existingDNSEntry != null) && (newDNSEntry.getKey().equals(existingDNSEntry.getKey()))) { 507 Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(newDNSEntry.getKey()); 508 509 List<DNSEntry> aNewValue = null; 510 if (oldEntry != null) { 511 aNewValue = new ArrayList<DNSEntry>(oldEntry.getValue()); 512 } else { 513 aNewValue = new ArrayList<DNSEntry>(); 514 } 515 aNewValue.remove(existingDNSEntry); 516 aNewValue.add(newDNSEntry); 517 518 if (oldEntry != null) { 519 oldEntry.setValue(aNewValue); 520 } else { 521 this.entrySet().add(new _CacheEntry(newDNSEntry.getKey(), aNewValue)); 522 } 523 // This is probably not very informative 524 result = true; 525 } 526 return result; 527 } 528 529 /** 530 * {@inheritDoc} 531 */ 532 @Override 533 public synchronized String toString() { 534 StringBuffer aLog = new StringBuffer(2000); 535 aLog.append("\t---- cache ----"); 536 for (Map.Entry<String, List<? extends DNSEntry>> entry : this.entrySet()) { 537 aLog.append("\n\t\t"); 538 aLog.append(entry.toString()); 539 } 540 return aLog.toString(); 541 } 542 543 } 544