DNS lookups for XMPP services : Initial Context « JNDI LDAP « Java






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();

  }

   
  








Related examples in the same category

1.how to create an initial context to an LDAP server using DIGEST-MD5 authentication
2.how to create an initial context to an LDAP server using DIGEST-MD5 authentication with a specified realm
3.how to create an initial context to an LDAP server using External authentication and SSL
4.how to create an initial context to an LDAP server using an LDAPS URL
5.how to create an initial context to an LDAP server using anonymous authentication (i.e. none)
6.how to create an initial context to an LDAP server using simple authentication
7.how to create an initial context to an LDAP server using SSL
8.how to create an initial context to an LDAP server using a custom socket factory
9.Creating an Initial Context to the Naming Service
10.Modify the attributes of the entry
11.Looking Up an Object from the Naming Service
12.Adding, Replacing, Removing, and Renaming a Binding in the Naming Service