1// Copyright 2003-2005 Arthur van Hoff Rick Blair
2// Licensed under Apache License version 2.0
3// Original license LGPL
4
5package javax.jmdns.impl;
6
7import java.util.AbstractMap;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16
17import javax.jmdns.impl.constants.DNSRecordClass;
18import 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 */
45public 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