JNDI LDAP Java

/**
 * $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.


     *
     * 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.


     *
     * 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.


 *
 * 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:


     * 
  •  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.
     * 
  •  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.

 * 


 * 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 implements Map {
    /**
     * The map the keys and values are stored in.
     */
    protected Map> 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.


     *
     * 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>(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 cacheObject = new CacheObject(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 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 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 values() {
        // First, clear all entries that have been in cache longer than the
        // maximum defined age.
        deleteExpiredEntries();
        return Collections.unmodifiableCollection(new AbstractCollection() {
            Collection> values = map.values();
            public Iterator iterator() {
                return new Iterator() {
                    Iterator> 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 map) {
        for (Entry 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) 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 cacheObject = new CacheObject((V) value);
        return map.containsValue(cacheObject);
    }
    public synchronized Set> 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>() {
            private final Set>> set = map.entrySet();
            public Iterator> iterator() {
                return new Iterator>() {
                    private final Iterator>> it = set.iterator();
                    public boolean hasNext() {
                        return it.hasNext();
                    }
                    public Entry next() {
                        Map.Entry> entry = it.next();
                        return new AbstractMapEntry(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 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 {
       /**
        * 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.


     */
    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.


         *
         * 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  extends AbstractKeyValue implements Map.Entry {
    /**
     * 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.
     * 


     * 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.
     * 


     * 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.
     * 


     * 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  implements KeyValue {
     /**
      * 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.
   * 


   * 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  {
      /**
       * Gets the key from the pair.
       *
       * @return the key
       */
      K getKey();
      /**
       * Gets the value from the pair.
       *
       * @return the value
       */
      V getValue();
  }