| // Copyright 2003-2005 Arthur van Hoff Rick Blair |
| // Licensed under Apache License version 2.0 |
| // Original license LGPL |
| |
| package javax.jmdns.impl; |
| |
| import java.util.AbstractMap; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.jmdns.impl.constants.DNSRecordClass; |
| import javax.jmdns.impl.constants.DNSRecordType; |
| |
| /** |
| * A table of DNS entries. This is a map table which can handle multiple entries with the same name. |
| * <p/> |
| * 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. |
| * <p/> |
| * Here's how to iterate over all entries: |
| * |
| * <pre> |
| * for (Iterator i=dnscache.allValues().iterator(); i.hasNext(); ) { |
| * DNSEntry entry = i.next(); |
| * ...do something with entry... |
| * } |
| * </pre> |
| * <p/> |
| * And here's how to iterate over all entries having a given name: |
| * |
| * <pre> |
| * for (Iterator i=dnscache.getDNSEntryList(name).iterator(); i.hasNext(); ) { |
| * DNSEntry entry = i.next(); |
| * ...do something with entry... |
| * } |
| * </pre> |
| * |
| * @author Arthur van Hoff, Werner Randelshofer, Rick Blair, Pierre Frisch |
| */ |
| public class DNSCache extends AbstractMap<String, List<? extends DNSEntry>> { |
| |
| // private static Logger logger = Logger.getLogger(DNSCache.class.getName()); |
| |
| private transient Set<Map.Entry<String, List<? extends DNSEntry>>> _entrySet = null; |
| |
| /** |
| * |
| */ |
| public static final DNSCache EmptyCache = new _EmptyCache(); |
| |
| static final class _EmptyCache extends DNSCache { |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int size() { |
| return 0; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean containsKey(Object key) { |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean containsValue(Object value) { |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<DNSEntry> get(Object key) { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Set<String> keySet() { |
| return Collections.emptySet(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Collection<List<? extends DNSEntry>> values() { |
| return Collections.emptySet(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Set<Map.Entry<String, List<? extends DNSEntry>>> entrySet() { |
| return Collections.emptySet(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(Object o) { |
| return (o instanceof Map) && ((Map<?, ?>) o).size() == 0; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<? extends DNSEntry> put(String key, List<? extends DNSEntry> value) { |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return 0; |
| } |
| |
| } |
| |
| /** |
| * |
| */ |
| protected static class _CacheEntry extends Object implements Map.Entry<String, List<? extends DNSEntry>> { |
| |
| private List<? extends DNSEntry> _value; |
| |
| private String _key; |
| |
| /** |
| * @param key |
| * @param value |
| */ |
| protected _CacheEntry(String key, List<? extends DNSEntry> value) { |
| super(); |
| _key = (key != null ? key.trim().toLowerCase() : null); |
| _value = value; |
| } |
| |
| /** |
| * @param entry |
| */ |
| protected _CacheEntry(Map.Entry<String, List<? extends DNSEntry>> entry) { |
| super(); |
| if (entry instanceof _CacheEntry) { |
| _key = ((_CacheEntry) entry).getKey(); |
| _value = ((_CacheEntry) entry).getValue(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getKey() { |
| return (_key != null ? _key : ""); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<? extends DNSEntry> getValue() { |
| return _value; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<? extends DNSEntry> setValue(List<? extends DNSEntry> value) { |
| List<? extends DNSEntry> oldValue = _value; |
| _value = value; |
| return oldValue; |
| } |
| |
| /** |
| * Returns <tt>true</tt> if this list contains no elements. |
| * |
| * @return <tt>true</tt> if this list contains no elements |
| */ |
| public boolean isEmpty() { |
| return this.getValue().isEmpty(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(Object entry) { |
| if (!(entry instanceof Map.Entry)) { |
| return false; |
| } |
| return this.getKey().equals(((Map.Entry<?, ?>) entry).getKey()) && this.getValue().equals(((Map.Entry<?, ?>) entry).getValue()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| return (_key == null ? 0 : _key.hashCode()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized String toString() { |
| StringBuffer aLog = new StringBuffer(200); |
| aLog.append("\n\t\tname '"); |
| aLog.append(_key); |
| aLog.append("' "); |
| if ((_value != null) && (!_value.isEmpty())) { |
| for (DNSEntry entry : _value) { |
| aLog.append("\n\t\t\t"); |
| aLog.append(entry.toString()); |
| } |
| } else { |
| aLog.append(" no entries"); |
| } |
| return aLog.toString(); |
| } |
| } |
| |
| /** |
| * |
| */ |
| public DNSCache() { |
| this(1024); |
| } |
| |
| /** |
| * @param map |
| */ |
| public DNSCache(DNSCache map) { |
| this(map != null ? map.size() : 1024); |
| if (map != null) { |
| this.putAll(map); |
| } |
| } |
| |
| /** |
| * Create a table with a given initial size. |
| * |
| * @param initialCapacity |
| */ |
| public DNSCache(int initialCapacity) { |
| super(); |
| _entrySet = new HashSet<Map.Entry<String, List<? extends DNSEntry>>>(initialCapacity); |
| } |
| |
| // ==================================================================== |
| // Map |
| |
| /* |
| * (non-Javadoc) |
| * @see java.util.AbstractMap#entrySet() |
| */ |
| @Override |
| public Set<Map.Entry<String, List<? extends DNSEntry>>> entrySet() { |
| if (_entrySet == null) { |
| _entrySet = new HashSet<Map.Entry<String, List<? extends DNSEntry>>>(); |
| } |
| return _entrySet; |
| } |
| |
| /** |
| * @param key |
| * @return map entry for the key |
| */ |
| protected Map.Entry<String, List<? extends DNSEntry>> getEntry(String key) { |
| String stringKey = (key != null ? key.trim().toLowerCase() : null); |
| for (Map.Entry<String, List<? extends DNSEntry>> entry : this.entrySet()) { |
| if (stringKey != null) { |
| if (stringKey.equals(entry.getKey())) { |
| return entry; |
| } |
| } else { |
| if (entry.getKey() == null) { |
| return entry; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public List<? extends DNSEntry> put(String key, List<? extends DNSEntry> value) { |
| synchronized (this) { |
| List<? extends DNSEntry> oldValue = null; |
| Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(key); |
| if (oldEntry != null) { |
| oldValue = oldEntry.setValue(value); |
| } else { |
| this.entrySet().add(new _CacheEntry(key, value)); |
| } |
| return oldValue; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected Object clone() throws CloneNotSupportedException { |
| return new DNSCache(this); |
| } |
| |
| // ==================================================================== |
| |
| /** |
| * Returns all entries in the cache |
| * |
| * @return all entries in the cache |
| */ |
| public synchronized Collection<DNSEntry> allValues() { |
| List<DNSEntry> allValues = new ArrayList<DNSEntry>(); |
| for (List<? extends DNSEntry> entry : this.values()) { |
| if (entry != null) { |
| allValues.addAll(entry); |
| } |
| } |
| return allValues; |
| } |
| |
| /** |
| * 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. |
| * |
| * @param name |
| * @return list of DNSEntries |
| */ |
| public synchronized Collection<? extends DNSEntry> getDNSEntryList(String name) { |
| Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); |
| if (entryList != null) { |
| entryList = new ArrayList<DNSEntry>(entryList); |
| } else { |
| entryList = Collections.emptyList(); |
| } |
| return entryList; |
| } |
| |
| private Collection<? extends DNSEntry> _getDNSEntryList(String name) { |
| return this.get(name != null ? name.toLowerCase() : null); |
| } |
| |
| /** |
| * Get a matching DNS entry from the table (using isSameEntry). Returns the entry that was found. |
| * |
| * @param dnsEntry |
| * @return DNSEntry |
| */ |
| public synchronized DNSEntry getDNSEntry(DNSEntry dnsEntry) { |
| DNSEntry result = null; |
| if (dnsEntry != null) { |
| Collection<? extends DNSEntry> entryList = this._getDNSEntryList(dnsEntry.getKey()); |
| if (entryList != null) { |
| for (DNSEntry testDNSEntry : entryList) { |
| if (testDNSEntry.isSameEntry(dnsEntry)) { |
| result = testDNSEntry; |
| break; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Get a matching DNS entry from the table. |
| * |
| * @param name |
| * @param type |
| * @param recordClass |
| * @return DNSEntry |
| */ |
| public synchronized DNSEntry getDNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass) { |
| DNSEntry result = null; |
| Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); |
| if (entryList != null) { |
| for (DNSEntry testDNSEntry : entryList) { |
| if (testDNSEntry.getRecordType().equals(type) && ((DNSRecordClass.CLASS_ANY == recordClass) || testDNSEntry.getRecordClass().equals(recordClass))) { |
| result = testDNSEntry; |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Get all matching DNS entries from the table. |
| * |
| * @param name |
| * @param type |
| * @param recordClass |
| * @return list of entries |
| */ |
| public synchronized Collection<? extends DNSEntry> getDNSEntryList(String name, DNSRecordType type, DNSRecordClass recordClass) { |
| Collection<? extends DNSEntry> entryList = this._getDNSEntryList(name); |
| if (entryList != null) { |
| entryList = new ArrayList<DNSEntry>(entryList); |
| for (Iterator<? extends DNSEntry> i = entryList.iterator(); i.hasNext();) { |
| DNSEntry testDNSEntry = i.next(); |
| if (!testDNSEntry.getRecordType().equals(type) || ((DNSRecordClass.CLASS_ANY != recordClass) && !testDNSEntry.getRecordClass().equals(recordClass))) { |
| i.remove(); |
| } |
| } |
| } else { |
| entryList = Collections.emptyList(); |
| } |
| return entryList; |
| } |
| |
| /** |
| * Adds an entry to the table. |
| * |
| * @param dnsEntry |
| * @return true if the entry was added |
| */ |
| public synchronized boolean addDNSEntry(final DNSEntry dnsEntry) { |
| boolean result = false; |
| if (dnsEntry != null) { |
| Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(dnsEntry.getKey()); |
| |
| List<DNSEntry> aNewValue = null; |
| if (oldEntry != null) { |
| aNewValue = new ArrayList<DNSEntry>(oldEntry.getValue()); |
| } else { |
| aNewValue = new ArrayList<DNSEntry>(); |
| } |
| aNewValue.add(dnsEntry); |
| |
| if (oldEntry != null) { |
| oldEntry.setValue(aNewValue); |
| } else { |
| this.entrySet().add(new _CacheEntry(dnsEntry.getKey(), aNewValue)); |
| } |
| // This is probably not very informative |
| result = true; |
| } |
| return result; |
| } |
| |
| /** |
| * Removes a specific entry from the table. Returns true if the entry was found. |
| * |
| * @param dnsEntry |
| * @return true if the entry was removed |
| */ |
| public synchronized boolean removeDNSEntry(DNSEntry dnsEntry) { |
| boolean result = false; |
| if (dnsEntry != null) { |
| Map.Entry<String, List<? extends DNSEntry>> existingEntry = this.getEntry(dnsEntry.getKey()); |
| if (existingEntry != null) { |
| result = existingEntry.getValue().remove(dnsEntry); |
| // If we just removed the last one we need to get rid of the entry |
| if (existingEntry.getValue().isEmpty()) { |
| this.entrySet().remove(existingEntry); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Replace an existing entry by a new one.<br/> |
| * <b>Note:</b> the 2 entries must have the same key. |
| * |
| * @param newDNSEntry |
| * @param existingDNSEntry |
| * @return <code>true</code> if the entry has been replace, <code>false</code> otherwise. |
| */ |
| public synchronized boolean replaceDNSEntry(DNSEntry newDNSEntry, DNSEntry existingDNSEntry) { |
| boolean result = false; |
| if ((newDNSEntry != null) && (existingDNSEntry != null) && (newDNSEntry.getKey().equals(existingDNSEntry.getKey()))) { |
| Map.Entry<String, List<? extends DNSEntry>> oldEntry = this.getEntry(newDNSEntry.getKey()); |
| |
| List<DNSEntry> aNewValue = null; |
| if (oldEntry != null) { |
| aNewValue = new ArrayList<DNSEntry>(oldEntry.getValue()); |
| } else { |
| aNewValue = new ArrayList<DNSEntry>(); |
| } |
| aNewValue.remove(existingDNSEntry); |
| aNewValue.add(newDNSEntry); |
| |
| if (oldEntry != null) { |
| oldEntry.setValue(aNewValue); |
| } else { |
| this.entrySet().add(new _CacheEntry(newDNSEntry.getKey(), aNewValue)); |
| } |
| // This is probably not very informative |
| result = true; |
| } |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized String toString() { |
| StringBuffer aLog = new StringBuffer(2000); |
| aLog.append("\t---- cache ----"); |
| for (Map.Entry<String, List<? extends DNSEntry>> entry : this.entrySet()) { |
| aLog.append("\n\t\t"); |
| aLog.append(entry.toString()); |
| } |
| return aLog.toString(); |
| } |
| |
| } |