// 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.
*
* 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.
*
* Here's how to iterate over all entries:
*
*
* for (Iterator i=dnscache.allValues().iterator(); i.hasNext(); ) {
* DNSEntry entry = i.next();
* ...do something with entry...
* }
*
*
* And here's how to iterate over all entries having a given name:
*
*
* for (Iterator i=dnscache.getDNSEntryList(name).iterator(); i.hasNext(); ) {
* DNSEntry entry = i.next();
* ...do something with entry...
* }
*
*
* @author Arthur van Hoff, Werner Randelshofer, Rick Blair, Pierre Frisch
*/
public class DNSCache extends AbstractMap> {
// private static Logger logger = Logger.getLogger(DNSCache.class.getName());
private transient Set>> _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 get(Object key) {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Set keySet() {
return Collections.emptySet();
}
/**
* {@inheritDoc}
*/
@Override
public Collection> values() {
return Collections.emptySet();
}
/**
* {@inheritDoc}
*/
@Override
public Set>> 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> {
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> 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 true if this list contains no elements.
*
* @return true 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>>(initialCapacity);
}
// ====================================================================
// Map
/*
* (non-Javadoc)
* @see java.util.AbstractMap#entrySet()
*/
@Override
public Set>> entrySet() {
if (_entrySet == null) {
_entrySet = new HashSet>>();
}
return _entrySet;
}
/**
* @param key
* @return map entry for the key
*/
protected Map.Entry> getEntry(String key) {
String stringKey = (key != null ? key.trim().toLowerCase() : null);
for (Map.Entry> 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> 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 allValues() {
List allValues = new ArrayList();
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(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(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> oldEntry = this.getEntry(dnsEntry.getKey());
List aNewValue = null;
if (oldEntry != null) {
aNewValue = new ArrayList(oldEntry.getValue());
} else {
aNewValue = new ArrayList();
}
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> 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.
* Note: the 2 entries must have the same key.
*
* @param newDNSEntry
* @param existingDNSEntry
* @return true
if the entry has been replace, false
otherwise.
*/
public synchronized boolean replaceDNSEntry(DNSEntry newDNSEntry, DNSEntry existingDNSEntry) {
boolean result = false;
if ((newDNSEntry != null) && (existingDNSEntry != null) && (newDNSEntry.getKey().equals(existingDNSEntry.getKey()))) {
Map.Entry> oldEntry = this.getEntry(newDNSEntry.getKey());
List aNewValue = null;
if (oldEntry != null) {
aNewValue = new ArrayList(oldEntry.getValue());
} else {
aNewValue = new ArrayList();
}
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> entry : this.entrySet()) {
aLog.append("\n\t\t");
aLog.append(entry.toString());
}
return aLog.toString();
}
}