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
size()62         public int size() {
63             return 0;
64         }
65 
66         /**
67          * {@inheritDoc}
68          */
69         @Override
isEmpty()70         public boolean isEmpty() {
71             return true;
72         }
73 
74         /**
75          * {@inheritDoc}
76          */
77         @Override
containsKey(Object key)78         public boolean containsKey(Object key) {
79             return false;
80         }
81 
82         /**
83          * {@inheritDoc}
84          */
85         @Override
containsValue(Object value)86         public boolean containsValue(Object value) {
87             return false;
88         }
89 
90         /**
91          * {@inheritDoc}
92          */
93         @Override
get(Object key)94         public List<DNSEntry> get(Object key) {
95             return null;
96         }
97 
98         /**
99          * {@inheritDoc}
100          */
101         @Override
keySet()102         public Set<String> keySet() {
103             return Collections.emptySet();
104         }
105 
106         /**
107          * {@inheritDoc}
108          */
109         @Override
values()110         public Collection<List<? extends DNSEntry>> values() {
111             return Collections.emptySet();
112         }
113 
114         /**
115          * {@inheritDoc}
116          */
117         @Override
entrySet()118         public Set<Map.Entry<String, List<? extends DNSEntry>>> entrySet() {
119             return Collections.emptySet();
120         }
121 
122         /**
123          * {@inheritDoc}
124          */
125         @Override
equals(Object o)126         public boolean equals(Object o) {
127             return (o instanceof Map) && ((Map<?, ?>) o).size() == 0;
128         }
129 
130         /**
131          * {@inheritDoc}
132          */
133         @Override
put(String key, List<? extends DNSEntry> value)134         public List<? extends DNSEntry> put(String key, List<? extends DNSEntry> value) {
135             return null;
136         }
137 
138         /**
139          * {@inheritDoc}
140          */
141         @Override
hashCode()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          */
_CacheEntry(String key, List<? extends DNSEntry> value)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          */
_CacheEntry(Map.Entry<String, List<? extends DNSEntry>> entry)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
getKey()182         public String getKey() {
183             return (_key != null ? _key : "");
184         }
185 
186         /**
187          * {@inheritDoc}
188          */
189         @Override
getValue()190         public List<? extends DNSEntry> getValue() {
191             return _value;
192         }
193 
194         /**
195          * {@inheritDoc}
196          */
197         @Override
setValue(List<? extends DNSEntry> value)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          */
isEmpty()209         public boolean isEmpty() {
210             return this.getValue().isEmpty();
211         }
212 
213         /**
214          * {@inheritDoc}
215          */
216         @Override
equals(Object entry)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
hashCode()228         public int hashCode() {
229             return (_key == null ? 0 : _key.hashCode());
230         }
231 
232         /**
233          * {@inheritDoc}
234          */
235         @Override
toString()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      */
DNSCache()256     public DNSCache() {
257         this(1024);
258     }
259 
260     /**
261      * @param map
262      */
DNSCache(DNSCache map)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      */
DNSCache(int initialCapacity)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
entrySet()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      */
getEntry(String key)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
put(String key, List<? extends DNSEntry> value)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
clone()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      */
allValues()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      */
getDNSEntryList(String name)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 
_getDNSEntryList(String name)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      */
getDNSEntry(DNSEntry dnsEntry)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      */
getDNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass)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      */
getDNSEntryList(String name, DNSRecordType type, DNSRecordClass recordClass)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      */
addDNSEntry(final DNSEntry dnsEntry)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      */
removeDNSEntry(DNSEntry dnsEntry)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      */
replaceDNSEntry(DNSEntry newDNSEntry, DNSEntry existingDNSEntry)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
toString()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