Mega Code Archive

 
Categories / Java / JNDI LDAP
 

DNS lookups for XMPP services

/**  * $Revision: 1456 $  * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $  *  * Copyright 2003-2005 Jive Software.  *  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */ import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; /**  * Utilty class to perform DNS lookups for XMPP services.  *  * @author Matt Tucker  */ public class DNSUtil {     /**      * Create a cache to hold the 100 most recently accessed DNS lookups for a period of      * 10 minutes.      */     private static Map cache = new Cache(100, 1000*60*10);     private static DirContext context;     static {         try {             Hashtable env = new Hashtable();             env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");             context = new InitialDirContext(env);         }         catch (Exception e) {             // Ignore.         }     }     /**      * Returns the host name and port that the specified XMPP server can be      * reached at for client-to-server communication. A DNS lookup for a SRV      * record in the form "_xmpp-client._tcp.example.com" is attempted, according      * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form      * of "_jabber._tcp.example.com" is attempted since servers that implement an      * older version of the protocol may be listed using that notation. If that      * lookup fails as well, it's assumed that the XMPP server lives at the      * host resolved by a DNS lookup at the specified domain on the default port      * of 5222.<p>      *      * As an example, a lookup for "example.com" may return "im.example.com:5269".      *      * @param domain the domain.      * @return a HostAddress, which encompasses the hostname and port that the XMPP      *      server can be reached at for the specified domain.      */     public static HostAddress resolveXMPPDomain(String domain) {         if (context == null) {             return new HostAddress(domain, 5222);         }         String key = "c" + domain;         // Return item from cache if it exists.         if (cache.containsKey(key)) {             HostAddress address = (HostAddress)cache.get(key);             if (address != null) {                 return address;             }         }         String host = domain;         int port = 5222;         try {             Attributes dnsLookup =                     context.getAttributes("_xmpp-client._tcp." + domain, new String[]{"SRV"});             String srvRecord = (String)dnsLookup.get("SRV").get();             String [] srvRecordEntries = srvRecord.split(" ");             port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);             host = srvRecordEntries[srvRecordEntries.length-1];         }         catch (Exception e) {             // Ignore.         }         // Host entries in DNS should end with a ".".         if (host.endsWith(".")) {             host = host.substring(0, host.length()-1);         }         HostAddress address = new HostAddress(host, port);         // Add item to cache.         cache.put(key, address);         return address;     }     /**      * Returns the host name and port that the specified XMPP server can be      * reached at for server-to-server communication. A DNS lookup for a SRV      * record in the form "_xmpp-server._tcp.example.com" is attempted, according      * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form      * of "_jabber._tcp.example.com" is attempted since servers that implement an      * older version of the protocol may be listed using that notation. If that      * lookup fails as well, it's assumed that the XMPP server lives at the      * host resolved by a DNS lookup at the specified domain on the default port      * of 5269.<p>      *      * As an example, a lookup for "example.com" may return "im.example.com:5269".      *      * @param domain the domain.      * @return a HostAddress, which encompasses the hostname and port that the XMPP      *      server can be reached at for the specified domain.      */     public static HostAddress resolveXMPPServerDomain(String domain) {         if (context == null) {             return new HostAddress(domain, 5269);         }         String key = "s" + domain;         // Return item from cache if it exists.         if (cache.containsKey(key)) {             HostAddress address = (HostAddress)cache.get(key);             if (address != null) {                 return address;             }         }         String host = domain;         int port = 5269;         try {             Attributes dnsLookup =                     context.getAttributes("_xmpp-server._tcp." + domain, new String[]{"SRV"});             String srvRecord = (String)dnsLookup.get("SRV").get();             String [] srvRecordEntries = srvRecord.split(" ");             port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);             host = srvRecordEntries[srvRecordEntries.length-1];         }         catch (Exception e) {             // Attempt lookup with older "jabber" name.             try {                 Attributes dnsLookup =                         context.getAttributes("_jabber._tcp." + domain, new String[]{"SRV"});                 String srvRecord = (String)dnsLookup.get("SRV").get();                 String [] srvRecordEntries = srvRecord.split(" ");                 port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);                 host = srvRecordEntries[srvRecordEntries.length-1];             }             catch (Exception e2) {                 // Ignore.             }         }         // Host entries in DNS should end with a ".".         if (host.endsWith(".")) {             host = host.substring(0, host.length()-1);         }         HostAddress address = new HostAddress(host, port);         // Add item to cache.         cache.put(key, address);         return address;     }     /**      * Encapsulates a hostname and port.      */     public static class HostAddress {         private String host;         private int port;         private HostAddress(String host, int port) {             this.host = host;             this.port = port;         }         /**          * Returns the hostname.          *          * @return the hostname.          */         public String getHost() {             return host;         }         /**          * Returns the port.          *          * @return the port.          */         public int getPort() {             return port;         }         public String toString() {             return host + ":" + port;         }         public boolean equals(Object o) {             if (this == o) {                 return true;             }             if (!(o instanceof HostAddress)) {                 return false;             }             final HostAddress address = (HostAddress) o;             if (!host.equals(address.host)) {                 return false;             }             return port == address.port;         }     } } /**  * A specialized Map that is size-limited (using an LRU algorithm) and  * has an optional expiration time for cache items. The Map is thread-safe.<p>  *  * The algorithm for cache is as follows: a HashMap is maintained for fast  * object lookup. Two linked lists are maintained: one keeps objects in the  * order they are accessed from cache, the other keeps objects in the order  * they were originally added to cache. When objects are added to cache, they  * are first wrapped by a CacheObject which maintains the following pieces  * of information:<ul>  * <li> A pointer to the node in the linked list that maintains accessed  * order for the object. Keeping a reference to the node lets us avoid  * linear scans of the linked list.  * <li> A pointer to the node in the linked list that maintains the age  * of the object in cache. Keeping a reference to the node lets us avoid  * linear scans of the linked list.</ul>  * <p/>  * To get an object from cache, a hash lookup is performed to get a reference  * to the CacheObject that wraps the real object we are looking for.  * The object is subsequently moved to the front of the accessed linked list  * and any necessary cache cleanups are performed. Cache deletion and expiration  * is performed as needed.  *  * @author Matt Tucker  */  class Cache<K, V> implements Map<K, V> {     /**      * The map the keys and values are stored in.      */     protected Map<K, CacheObject<V>> map;     /**      * Linked list to maintain order that cache objects are accessed      * in, most used to least used.      */     protected LinkedList lastAccessedList;     /**      * Linked list to maintain time that cache objects were initially added      * to the cache, most recently added to oldest added.      */     protected LinkedList ageList;     /**      * Maximum number of items the cache will hold.      */     protected int maxCacheSize;     /**      * Maximum length of time objects can exist in cache before expiring.      */     protected long maxLifetime;     /**      * Maintain the number of cache hits and misses. A cache hit occurs every      * time the get method is called and the cache contains the requested      * object. A cache miss represents the opposite occurence.<p>      *      * Keeping track of cache hits and misses lets one measure how efficient      * the cache is; the higher the percentage of hits, the more efficient.      */     protected long cacheHits, cacheMisses = 0L;     /**      * Create a new cache and specify the maximum size of for the cache in      * bytes, and the maximum lifetime of objects.      *      * @param maxSize the maximum number of objects the cache will hold. -1      *      means the cache has no max size.      * @param maxLifetime the maximum amount of time (in ms) objects can exist in      *      cache before being deleted. -1 means objects never expire.      */     public Cache(int maxSize, long maxLifetime) {         if (maxSize == 0) {             throw new IllegalArgumentException("Max cache size cannot be 0.");         }         this.maxCacheSize = maxSize;         this.maxLifetime = maxLifetime;         // Our primary data structure is a hash map. The default capacity of 11         // is too small in almost all cases, so we set it bigger.         map = new HashMap<K, CacheObject<V>>(103);         lastAccessedList = new LinkedList();         ageList = new LinkedList();     }     public synchronized V put(K key, V value) {         V oldValue = null;         // Delete an old entry if it exists.         if (map.containsKey(key)) {             oldValue = remove(key, true);         }         CacheObject<V> cacheObject = new CacheObject<V>(value);         map.put(key, cacheObject);         // Make an entry into the cache order list.         // Store the cache order list entry so that we can get back to it         // during later lookups.         cacheObject.lastAccessedListNode = lastAccessedList.addFirst(key);         // Add the object to the age list         LinkedListNode ageNode = ageList.addFirst(key);         ageNode.timestamp = System.currentTimeMillis();         cacheObject.ageListNode = ageNode;         // If cache is too full, remove least used cache entries until it is not too full.         cullCache();         return oldValue;     }     public synchronized V get(Object key) {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         CacheObject<V> cacheObject = map.get(key);         if (cacheObject == null) {             // The object didn't exist in cache, so increment cache misses.             cacheMisses++;             return null;         }         // Remove the object from it's current place in the cache order list,         // and re-insert it at the front of the list.         cacheObject.lastAccessedListNode.remove();         lastAccessedList.addFirst(cacheObject.lastAccessedListNode);         // The object exists in cache, so increment cache hits. Also, increment         // the object's read count.         cacheHits++;         cacheObject.readCount++;         return cacheObject.object;     }     public synchronized V remove(Object key) {         return remove(key, false);     }     /*      * Remove operation with a flag so we can tell coherence if the remove was      * caused by cache internal processing such as eviction or loading      */     public synchronized V remove(Object key, boolean internal) {         //noinspection SuspiciousMethodCalls         CacheObject<V> cacheObject =  map.remove(key);         // If the object is not in cache, stop trying to remove it.         if (cacheObject == null) {             return null;         }         // Remove from the cache order list         cacheObject.lastAccessedListNode.remove();         cacheObject.ageListNode.remove();         // Remove references to linked list nodes         cacheObject.ageListNode = null;         cacheObject.lastAccessedListNode = null;         return cacheObject.object;     }     public synchronized void clear() {         Object[] keys = map.keySet().toArray();         for (Object key : keys) {             remove(key);         }         // Now, reset all containers.         map.clear();         lastAccessedList.clear();         ageList.clear();         cacheHits = 0;         cacheMisses = 0;     }     public synchronized int size() {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return map.size();     }     public synchronized boolean isEmpty() {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return map.isEmpty();     }     public synchronized Collection<V> values() {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return Collections.unmodifiableCollection(new AbstractCollection<V>() {             Collection<CacheObject<V>> values = map.values();             public Iterator<V> iterator() {                 return new Iterator<V>() {                     Iterator<CacheObject<V>> it = values.iterator();                     public boolean hasNext() {                         return it.hasNext();                     }                     public V next() {                         return it.next().object;                     }                     public void remove() {                         it.remove();                     }                 };             }             public int size() {                 return values.size();             }         });     }     public synchronized boolean containsKey(Object key) {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return map.containsKey(key);     }     public void putAll(Map<? extends K, ? extends V> map) {         for (Entry<? extends K, ? extends V> entry : map.entrySet()) {             V value = entry.getValue();             // If the map is another DefaultCache instance than the             // entry values will be CacheObject instances that need             // to be converted to the normal object form.             if (value instanceof CacheObject) {                 //noinspection unchecked                 value = ((CacheObject<V>) value).object;             }             put(entry.getKey(), value);         }     }     public synchronized boolean containsValue(Object value) {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         //noinspection unchecked         CacheObject<V> cacheObject = new CacheObject<V>((V) value);         return map.containsValue(cacheObject);     }     public synchronized Set<Map.Entry<K, V>> entrySet() {         // Warning -- this method returns CacheObject instances and not Objects         // in the same form they were put into cache.         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return new AbstractSet<Map.Entry<K, V>>() {             private final Set<Map.Entry<K, CacheObject<V>>> set = map.entrySet();             public Iterator<Entry<K, V>> iterator() {                 return new Iterator<Entry<K, V>>() {                     private final Iterator<Entry<K, CacheObject<V>>> it = set.iterator();                     public boolean hasNext() {                         return it.hasNext();                     }                     public Entry<K, V> next() {                         Map.Entry<K, CacheObject<V>> entry = it.next();                         return new AbstractMapEntry<K, V>(entry.getKey(), entry.getValue().object) {                             @Override                             public V setValue(V value) {                                 throw new UnsupportedOperationException("Cannot set");                             }                         };                     }                     public void remove() {                         it.remove();                     }                 };             }             public int size() {                 return set.size();             }         };     }     public synchronized Set<K> keySet() {         // First, clear all entries that have been in cache longer than the         // maximum defined age.         deleteExpiredEntries();         return Collections.unmodifiableSet(map.keySet());     }     public long getCacheHits() {         return cacheHits;     }     public long getCacheMisses() {         return cacheMisses;     }     public int getMaxCacheSize() {         return maxCacheSize;     }     public synchronized void setMaxCacheSize(int maxCacheSize) {         this.maxCacheSize = maxCacheSize;         // It's possible that the new max size is smaller than our current cache         // size. If so, we need to delete infrequently used items.         cullCache();     }     public long getMaxLifetime() {         return maxLifetime;     }     public void setMaxLifetime(long maxLifetime) {         this.maxLifetime = maxLifetime;     }     /**      * Clears all entries out of cache where the entries are older than the      * maximum defined age.      */     protected synchronized void deleteExpiredEntries() {         // Check if expiration is turned on.         if (maxLifetime <= 0) {             return;         }         // Remove all old entries. To do this, we remove objects from the end         // of the linked list until they are no longer too old. We get to avoid         // any hash lookups or looking at any more objects than is strictly         // neccessary.         LinkedListNode node = ageList.getLast();         // If there are no entries in the age list, return.         if (node == null) {             return;         }         // Determine the expireTime, which is the moment in time that elements         // should expire from cache. Then, we can do an easy check to see         // if the expire time is greater than the expire time.         long expireTime = System.currentTimeMillis() - maxLifetime;         while (expireTime > node.timestamp) {             if (remove(node.object, true) == null) {                 System.err.println("Error attempting to remove(" + node.object.toString() +                 ") - cacheObject not found in cache!");                 // remove from the ageList                 node.remove();             }             // Get the next node.             node = ageList.getLast();             // If there are no more entries in the age list, return.             if (node == null) {                 return;             }         }     }     /**      * Removes the least recently used elements if the cache size is greater than      * or equal to the maximum allowed size until the cache is at least 10% empty.      */     protected synchronized void cullCache() {         // Check if a max cache size is defined.         if (maxCacheSize < 0) {             return;         }         // See if the cache is too big. If so, clean out cache until it's 10% free.         if (map.size() > maxCacheSize) {             // First, delete any old entries to see how much memory that frees.             deleteExpiredEntries();             // Next, delete the least recently used elements until 10% of the cache             // has been freed.             int desiredSize = (int) (maxCacheSize * .90);             for (int i=map.size(); i>desiredSize; i--) {                 // Get the key and invoke the remove method on it.                 if (remove(lastAccessedList.getLast().object, true) == null) {                     System.err.println("Error attempting to cullCache with remove(" +                             lastAccessedList.getLast().object.toString() + ") - " +                             "cacheObject not found in cache!");                     lastAccessedList.getLast().remove();                 }             }         }     }     /**      * Wrapper for all objects put into cache. It's primary purpose is to maintain      * references to the linked lists that maintain the creation time of the object      * and the ordering of the most used objects.      *      * This class is optimized for speed rather than strictly correct encapsulation.      */     private static class CacheObject<V> {        /**         * Underlying object wrapped by the CacheObject.         */         public V object;         /**          * A reference to the node in the cache order list. We keep the reference          * here to avoid linear scans of the list. Every time the object is          * accessed, the node is removed from its current spot in the list and          * moved to the front.          */         public LinkedListNode lastAccessedListNode;         /**          * A reference to the node in the age order list. We keep the reference          * here to avoid linear scans of the list. The reference is used if the          * object has to be deleted from the list.          */         public LinkedListNode ageListNode;         /**          * A count of the number of times the object has been read from cache.          */         public int readCount = 0;         /**          * Creates a new cache object wrapper.          *          * @param object the underlying Object to wrap.          */         public CacheObject(V object) {             this.object = object;         }         public boolean equals(Object o) {             if (this == o) {                 return true;             }             if (!(o instanceof CacheObject)) {                 return false;             }             final CacheObject cacheObject = (CacheObject) o;             return object.equals(cacheObject.object);         }         public int hashCode() {             return object.hashCode();         }     }     /**      * Simple LinkedList implementation. The main feature is that list nodes      * are public, which allows very fast delete operations when one has a      * reference to the node that is to be deleted.<p>      */     private static class LinkedList {         /**          * The root of the list keeps a reference to both the first and last          * elements of the list.          */         private LinkedListNode head = new LinkedListNode("head", null, null);         /**          * Creates a new linked list.          */         public LinkedList() {             head.next = head.previous = head;         }         /**          * Returns the first linked list node in the list.          *          * @return the first element of the list.          */         public LinkedListNode getFirst() {             LinkedListNode node = head.next;             if (node == head) {                 return null;             }             return node;         }         /**          * Returns the last linked list node in the list.          *          * @return the last element of the list.          */         public LinkedListNode getLast() {             LinkedListNode node = head.previous;             if (node == head) {                 return null;             }             return node;         }         /**          * Adds a node to the beginning of the list.          *          * @param node the node to add to the beginning of the list.          * @return the node          */         public LinkedListNode addFirst(LinkedListNode node) {             node.next = head.next;             node.previous = head;             node.previous.next = node;             node.next.previous = node;             return node;         }         /**          * Adds an object to the beginning of the list by automatically creating a          * a new node and adding it to the beginning of the list.          *          * @param object the object to add to the beginning of the list.          * @return the node created to wrap the object.          */         public LinkedListNode addFirst(Object object) {             LinkedListNode node = new LinkedListNode(object, head.next, head);             node.previous.next = node;             node.next.previous = node;             return node;         }         /**          * Adds an object to the end of the list by automatically creating a          * a new node and adding it to the end of the list.          *          * @param object the object to add to the end of the list.          * @return the node created to wrap the object.          */         public LinkedListNode addLast(Object object) {             LinkedListNode node = new LinkedListNode(object, head, head.previous);             node.previous.next = node;             node.next.previous = node;             return node;         }         /**          * Erases all elements in the list and re-initializes it.          */         public void clear() {             //Remove all references in the list.             LinkedListNode node = getLast();             while (node != null) {                 node.remove();                 node = getLast();             }             //Re-initialize.             head.next = head.previous = head;         }         /**          * Returns a String representation of the linked list with a comma          * delimited list of all the elements in the list.          *          * @return a String representation of the LinkedList.          */         public String toString() {             LinkedListNode node = head.next;             StringBuilder buf = new StringBuilder();             while (node != head) {                 buf.append(node.toString()).append(", ");                 node = node.next;             }             return buf.toString();         }     }     /**      * Doubly linked node in a LinkedList. Most LinkedList implementations keep the      * equivalent of this class private. We make it public so that references      * to each node in the list can be maintained externally.      *      * Exposing this class lets us make remove operations very fast. Remove is      * built into this class and only requires two reference reassignments. If      * remove existed in the main LinkedList class, a linear scan would have to      * be performed to find the correct node to delete.      *      * The linked list implementation was specifically written for the Jive      * cache system. While it can be used as a general purpose linked list, for      * most applications, it is more suitable to use the linked list that is part      * of the Java Collections package.      */     private static class LinkedListNode {         public LinkedListNode previous;         public LinkedListNode next;         public Object object;         /**          * This class is further customized for the Jive cache system. It          * maintains a timestamp of when a Cacheable object was first added to          * cache. Timestamps are stored as long values and represent the number          * of milliseconds passed since January 1, 1970 00:00:00.000 GMT.<p>          *          * The creation timestamp is used in the case that the cache has a          * maximum lifetime set. In that case, when          * [current time] - [creation time] > [max lifetime], the object will be          * deleted from cache.          */         public long timestamp;         /**          * Constructs a new linked list node.          *          * @param object the Object that the node represents.          * @param next a reference to the next LinkedListNode in the list.          * @param previous a reference to the previous LinkedListNode in the list.          */         public LinkedListNode(Object object, LinkedListNode next,                 LinkedListNode previous)         {             this.object = object;             this.next = next;             this.previous = previous;         }         /**          * Removes this node from the linked list that it is a part of.          */         public void remove() {             previous.next = next;             next.previous = previous;         }         /**          * Returns a String representation of the linked list node by calling the          * toString method of the node's object.          *          * @return a String representation of the LinkedListNode.          */         public String toString() {             return object.toString();         }     } } //GenericsNote: Converted. /*  *  Copyright 2003-2004 The Apache Software Foundation  *  *  Licensed under the Apache License, Version 2.0 (the "License");  *  you may not use this file except in compliance with the License.  *  You may obtain a copy of the License at  *  *      http://www.apache.org/licenses/LICENSE-2.0  *  *  Unless required by applicable law or agreed to in writing, software  *  distributed under the License is distributed on an "AS IS" BASIS,  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *  See the License for the specific language governing permissions and  *  limitations under the License.  */ /**  * Abstract Pair class to assist with creating correct Map Entry implementations.  *  * @author James Strachan  * @author Michael A. Smith  * @author Neil O'Toole  * @author Matt Hall, John Watkinson, Stephen Colebourne  * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $  * @since Commons Collections 3.0  */  abstract class AbstractMapEntry <K,V> extends AbstractKeyValue<K, V> implements Map.Entry<K, V> {     /**      * Constructs a new entry with the given key and given value.      *      * @param key   the key for the entry, may be null      * @param value the value for the entry, may be null      */     protected AbstractMapEntry(K key, V value) {         super(key, value);     }     // Map.Entry interface     //-------------------------------------------------------------------------     /**      * Sets the value stored in this Map Entry.      * <p/>      * This Map Entry is not connected to a Map, so only the local data is changed.      *      * @param value the new value      * @return the previous value      */     public V setValue(V value) {         V answer = this.value;         this.value = value;         return answer;     }     /**      * Compares this Map Entry with another Map Entry.      * <p/>      * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)}      *      * @param obj the object to compare to      * @return true if equal key and value      */     public boolean equals(Object obj) {         if (obj == this) {             return true;         }         if (obj instanceof Map.Entry == false) {             return false;         }         Map.Entry other = (Map.Entry) obj;         return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));     }     /**      * Gets a hashCode compatible with the equals method.      * <p/>      * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}      *      * @return a suitable hash code      */     public int hashCode() {         return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode());     } } //GenericsNote: Converted.  /*   *  Copyright 2003-2004 The Apache Software Foundation   *   *  Licensed under the Apache License, Version 2.0 (the "License");   *  you may not use this file except in compliance with the License.   *  You may obtain a copy of the License at   *   *      http://www.apache.org/licenses/LICENSE-2.0   *   *  Unless required by applicable law or agreed to in writing, software   *  distributed under the License is distributed on an "AS IS" BASIS,   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *  See the License for the specific language governing permissions and   *  limitations under the License.   */  /**   * Abstract pair class to assist with creating KeyValue and MapEntry implementations.   *   * @author James Strachan   * @author Michael A. Smith   * @author Neil O'Toole   * @author Matt Hall, John Watkinson, Stephen Colebourne   * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $   * @since Commons Collections 3.0   */   abstract class AbstractKeyValue <K,V> implements KeyValue<K, V> {      /**       * The key       */      protected K key;      /**       * The value       */      protected V value;      /**       * Constructs a new pair with the specified key and given value.       *       * @param key   the key for the entry, may be null       * @param value the value for the entry, may be null       */      protected AbstractKeyValue(K key, V value) {          super();          this.key = key;          this.value = value;      }      /**       * Gets the key from the pair.       *       * @return the key       */      public K getKey() {          return key;      }      /**       * Gets the value from the pair.       *       * @return the value       */      public V getValue() {          return value;      }      /**       * Gets a debugging String view of the pair.       *       * @return a String view of the entry       */      public String toString() {          return new StringBuilder().append(getKey()).append('=').append(getValue()).toString();      }  } //GenericsNote: Converted.   /*    *  Copyright 2003-2004 The Apache Software Foundation    *    *  Licensed under the Apache License, Version 2.0 (the "License");    *  you may not use this file except in compliance with the License.    *  You may obtain a copy of the License at    *    *      http://www.apache.org/licenses/LICENSE-2.0    *    *  Unless required by applicable law or agreed to in writing, software    *  distributed under the License is distributed on an "AS IS" BASIS,    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.    *  See the License for the specific language governing permissions and    *  limitations under the License.    */   /**    * Defines a simple key value pair.    * <p/>    * A Map Entry has considerable additional semantics over and above a simple    * key-value pair. This interface defines the minimum key value, with just the    * two get methods.    *    * @author Matt Hall, John Watkinson, Stephen Colebourne    * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $    * @since Commons Collections 3.0    */    interface KeyValue <K,V> {       /**        * Gets the key from the pair.        *        * @return the key        */       K getKey();       /**        * Gets the value from the pair.        *        * @return the value        */       V getValue();   }