View Javadoc

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 }