org.ccnx.ccn.profiles.nameenum.EnumeratedNameList.java Source code

Java tutorial

Introduction

Here is the source code for org.ccnx.ccn.profiles.nameenum.EnumeratedNameList.java

Source

/*
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008-2010, 2012, 2013 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received
 * a copy of the GNU Lesser General Public License along with this library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.profiles.nameenum;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;

import org.bouncycastle.util.Arrays;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.protocol.CCNTime;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentNameProvider;

/**
 * Blocking and background interface to name enumeration. This allows a caller to specify a prefix
 * under which to enumerate available children, and name enumeration to proceed in the background
 * for as long as desired, providing updates whenever new data is published.
 * Currently implemented as a wrapper around CCNNameEnumerator, will likely directly aggregate
 * name enumeration responses in the future.
 *
 * @see CCNNameEnumerator
 * @see BasicNameEnumeratorListener
 */
public class EnumeratedNameList implements BasicNameEnumeratorListener, ContentNameProvider {

    protected ContentName _namePrefix;
    protected CCNNameEnumerator _enumerator;
    protected BasicNameEnumeratorListener callback;
    // make these contain something other than content names when the enumerator has better data types
    protected SortedSet<ContentName> _children = new TreeSet<ContentName>();
    //protected SortedSet<ContentName> _newChildren = null;
    protected Map<Long, NewChildrenByThread> _newChildrenByThread = new TreeMap<Long, NewChildrenByThread>();
    protected Object _childLock = new Object();
    protected CCNTime _lastUpdate = null;
    protected boolean _enumerating = false;
    protected boolean _shutdown = false;

    private class NewChildrenByThread implements Comparable<NewChildrenByThread> {
        private final Long _id; // If 0 this is in thread pool mode
        private SortedSet<ContentName> _newChildren = null;

        private NewChildrenByThread(Long id) {
            this._id = id;
        }

        public int compareTo(NewChildrenByThread o) {
            return _id.compareTo(o._id);
        }
    }

    /**
     * Keep track of whether we've ever done enumeration, so we can start it automatically
     * if someone asks us for something not in our cache. This lets us relax the requirement
     * that callers pre-enumerate just in case. Can't use hasChildren; there might not
     * be any children but we might have tried enumeration already.
     */
    protected boolean _hasEnumerated = false;

    /**
     * Creates an EnumeratedNameList object
     *
     * This constructor creates a new EnumeratedNameList object that will begin enumerating
     * the children of the specified prefix.  The new EnumeratedNameList will use the CCNHandle passed
     * in to the constructor, or create a new one using CCNHandle#open() if it is null.
     *
     * @param  namePrefix the ContentName whose children we wish to list.
     * @param  handle the CCNHandle object for sending interests and receiving content object responses.
     */
    public EnumeratedNameList(ContentName namePrefix, CCNHandle handle) throws IOException {
        this(namePrefix, true, handle);
    }

    public EnumeratedNameList(ContentName namePrefix, boolean startEnumerating, CCNHandle handle)
            throws IOException {
        if (null == namePrefix) {
            throw new IllegalArgumentException("namePrefix cannot be null!");
        }
        if (null == handle) {
            try {
                handle = CCNHandle.open();
            } catch (ConfigurationException e) {
                throw new IOException("ConfigurationException attempting to open a handle: " + e.getMessage());
            }
        }
        _namePrefix = namePrefix;
        if (startEnumerating) {
            _enumerating = true;
            _hasEnumerated = true;
            _enumerator = new CCNNameEnumerator(namePrefix, handle, this);
        } else {
            _enumerator = new CCNNameEnumerator(handle, this);
        }
    }

    /**
     * Method to return the ContentName used for enumeration.
     *
     * @return ContentName returns the prefix under which we are enumerating children.
     */
    public ContentName getName() {
        return _namePrefix;
    }

    /**
     * Cancels ongoing name enumeration. Previously-accumulated information about
     * children of this name are still stored and available for use.
     *
     * @return void
     * */
    public synchronized void stopEnumerating() {
        if (!_enumerating) {
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
                Log.info(Log.FAC_SEARCH, "Enumerated name list: Not enumerating, so not canceling prefix.");
            }
            return;
        }
        _enumerator.cancelPrefix(_namePrefix);
        _enumerating = false;
    }

    /**
     * Starts enumeration, if we're not enumerating already.
     * @throws IOException
     */
    public synchronized void startEnumerating() throws IOException {
        _enumerating = true;
        _enumerator.registerPrefix(_namePrefix);
        _hasEnumerated = true;
    }

    public boolean isEnumerating() {
        return _enumerating;
    }

    public boolean hasEnumerated() {
        return _hasEnumerated;
    }

    /**
     * Shutdown anybody waiting for children on this list
     */
    public void shutdown() {
        _shutdown = true;
        synchronized (_childLock) {
            _childLock.notifyAll();
        }
    }

    /**
     * Interface to retrieve only new data from enumeration responses as it arrives.
     * This method blocks and waits for data, but grabs the new data for processing.
     * In threadPoolContext it will in effect remove the data from every other
     * listener who is listening in threadPoolContext, in effect handing the new
     * children to the first consumer to wake up and make the other ones go around again.
     * There is currently no support for more than one simultaneous thread pool.
     *
     * @param threadPoolContext Are we getting data in threadPoolContext? (described above).
     * @param timeout maximum amount of time to wait, 0 to wait forever.
     * @return SortedSet<ContentName> Returns the array of single-component
     *    content name children that are new to us, or null if we reached the
     *  timeout before new data arrived
     */
    public SortedSet<ContentName> getNewData(boolean threadPoolContext, long timeout) {
        SortedSet<ContentName> childArray = null;
        synchronized (_childLock) { // reentrant
            Long id = threadPoolContext ? 0 : Thread.currentThread().getId();
            NewChildrenByThread ncbt = _newChildrenByThread.get(id);
            SortedSet<ContentName> newChildren = ncbt == null ? null : ncbt._newChildren;
            while ((null == newChildren) || newChildren.size() == 0) {
                waitForNewChildren(threadPoolContext, timeout);
                ncbt = _newChildrenByThread.get(id);
                newChildren = ncbt._newChildren;
                if (timeout != SystemConfiguration.NO_TIMEOUT)
                    break;
            }

            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
                Log.info(Log.FAC_SEARCH, "Waiting for new data on prefix: {0} got {1}.", _namePrefix,
                        ((null == newChildren) ? 0 : newChildren.size()));
            }

            if (null != newChildren) {
                childArray = newChildren;
                ncbt._newChildren = null;
            }
        }
        return childArray;
    }

    /**
     * Block and wait as long as it takes for new data to appear. See #getNewData(boolean, long).
     * @return SortedSet<ContentName> Returns the array of single-component
     *    content name children that are new to us. Waits forever if no new data appears
     */
    public SortedSet<ContentName> getNewData() {
        return getNewData(false, SystemConfiguration.NO_TIMEOUT);
    }

    /**
     * Block and wait for timeout or until new data appears. See #getNewData(boolean, long).
     * @param timeout in ms
     * @return SortedSet<ContentName> Returns the array of single-component
     *    content name children that are new to us, or null if we reached the
     *  timeout before new data arrived
     */
    public SortedSet<ContentName> getNewData(long timeout) {
        return getNewData(false, timeout);
    }

    /**
     * Block and wait for timeout or until new data appears. See #getNewData(boolean, long).
     * Different from getNewData in that new data is shared among all threads accessing this
     * instance of EnumeratedNameList. So if another thread gets the data first, we won't get it.
     * @param timeout in ms
     * @return SortedSet<ContentName> Returns the array of single-component
     *    content name children that are new to the list instance if we got it first, or null if we
     *  reached the timeout before new data arrived
     */
    public SortedSet<ContentName> getNewDataThreadPool(long timeout) {
        return getNewData(true, timeout);
    }

    /**
     * Returns single-component ContentName objects containing the name components of the children.
     * @return SortedSet<ContentName> Returns the array of single-component
     *    content name children that have been retrieved so far, or null if no responses
     *  have yet been received. The latter may indicate either that no children of this prefix
     *  are known to any responders, or that they have not had time to respond.
     */
    public SortedSet<ContentName> getChildren() {
        if (!hasChildren())
            return null;
        return _children;
    }

    /**
     * Returns true if the prefix has new names that have not been handled by the calling application.
     * @return true if there are new children available to process
     */
    public boolean hasNewData() {
        NewChildrenByThread ncbt = getNcbt();
        if (null == ncbt)
            return false; // Never set up
        return ((null != ncbt._newChildren) && (ncbt._newChildren.size() > 0));
    }

    /**
     * Returns true if we have received any responses listing available child names.
     * If no names have yet been received, this may mean either that responses
     * have not had time to arrive, or there are know children known to available
     * responders.
     *
     * @return true if we have child names received from enumeration responses
     */
    public boolean hasChildren() {
        return ((null != _children) && (_children.size() > 0));
    }

    /**
     * Returns the number of children we have, or 0 if we have none.
     */
    public int childCount() {
        if (null == _children)
            return 0;
        return _children.size();
    }

    /**
     * Returns true if we know the prefix has a child matching the given name component.
     *
     * @param childComponent name component to check for in the stored child names.
     * @return true if that child is in our list of known children
     */
    public boolean hasChild(byte[] childComponent) {
        for (ContentName child : _children) {
            if (Arrays.areEqual(childComponent, child.component(0))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns whether a child is present in the list of known children.
     * <p>
     *
     * @param childName String version of a child name to look for
     * @return boolean Returns true if the name is present in the list of known children.
     * */
    public boolean hasChild(String childName) {
        return hasChild(Component.parseNative(childName));
    }

    /**
     * Wait for new children to arrive.
     *
     * @param timeout Maximum time to wait for new data.
     * @param threadPoolContext Are we waiting in threadPoolContext (i.e. other threads can grab children first)
     *        See #getNewData(boolean, long).
     * @return a boolean value that indicates whether new data was found.
     */
    public boolean waitForNewChildren(boolean threadPoolContext, long timeout) {
        boolean foundNewData = false;

        Long id = threadPoolContext ? 0 : Thread.currentThread().getId();
        _newChildrenByThread.put(id, new NewChildrenByThread(id));
        synchronized (_childLock) {
            CCNTime lastUpdate = _lastUpdate;
            long timeRemaining = timeout;
            long startTime = System.currentTimeMillis();
            while (((null == _lastUpdate) || ((null != lastUpdate) && !_lastUpdate.after(lastUpdate)))
                    && ((timeout == SystemConfiguration.NO_TIMEOUT) || (timeRemaining > 0))) {
                if (_shutdown)
                    break;
                try {
                    _childLock.wait((timeout != SystemConfiguration.NO_TIMEOUT)
                            ? Math.min(timeRemaining, SystemConfiguration.CHILD_WAIT_INTERVAL)
                            : SystemConfiguration.CHILD_WAIT_INTERVAL);
                    if (timeout != SystemConfiguration.NO_TIMEOUT) {
                        timeRemaining = timeout - (System.currentTimeMillis() - startTime);
                    }
                } catch (InterruptedException e) {
                }
                if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
                    SortedSet<ContentName> newChildren = _newChildrenByThread.get(id)._newChildren;
                    Log.info(Log.FAC_SEARCH,
                            "Waiting for new data on prefix: {0}, updated {1}, our update {2}, have {3} children {4} new.",
                            _namePrefix, _lastUpdate, lastUpdate, ((null == _children) ? 0 : _children.size()),
                            ((null == newChildren) ? 0 : newChildren.size()));
                }
            }
            if ((null != _lastUpdate) && ((null == lastUpdate) || (_lastUpdate.after(lastUpdate))))
                foundNewData = true;
        }
        return foundNewData;
    }

    /**
     * Wait for new children to arrive.
     * This method does not have a timeout and will wait forever.
     *
     * @return void
     */
    public void waitForNewChildren() {
        waitForNewChildren(false, SystemConfiguration.NO_TIMEOUT);
    }

    /**
     * Wait for new children to arrive.
     *
     * @param timeout Maximum amount of time to wait, if 0, waits forever.
     * @return a boolean value that indicates whether new data was found.
     */
    public boolean waitForNewChildren(long timeout) {
        return waitForNewChildren(false, timeout);
    }

    /**
     * Wait for new children to arrive in thread pool context.  See notes about this above.
     * @param timeout Maximum amount of time to wait, if 0, waits forever.
     * @return a boolean value that indicates whether new data was found.
     */
    public boolean waitForNewChildrenThreadPool(long timeout) {
        return waitForNewChildren(true, timeout);
    }

    /**
     * Waits until there is any data at all. Right now, waits for the first response containing actual
     * children, not just a name enumeration response. That means it could block
     * forever if no children exist in a repository or there are not any applications responding to
     * name enumeration requests. Once we have an initial set of children, this method
     * returns immediately.
     *
     * @param timeout Maximum amount of time to wait, if 0, waits forever.
     * @return void
     */
    public void waitForChildren(long timeout) {
        while ((null == _children) || _children.size() == 0) {
            waitForNewChildren(false, timeout);
            if (timeout != SystemConfiguration.NO_TIMEOUT)
                break;
        }
    }

    /**
     * Wait (block) for initial data to arrive, possibly forever. See #waitForData(long).
     *
     * @return void
     *
     */
    public void waitForChildren() {
        waitForChildren(SystemConfiguration.NO_TIMEOUT);
    }

    /**
     * Wait for new children to arrive until there is a period of length timeout during which
     * no new child arrives.
     * @param timeout The maximum amount of time to wait between consecutive children arrivals.
     */
    public void waitForNoUpdates(long timeout) {
        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH,
                    "Waiting for updates on prefix {0} with max timeout of {1} ms between consecutive children arrivals.",
                    _namePrefix, timeout);
        long startTime = System.currentTimeMillis();
        while (waitForNewChildren(false, timeout)) {
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                Log.info(Log.FAC_SEARCH, "Child or children found on prefix {0}", _namePrefix);
        }
        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH, "Quit waiting for updates on prefix {0} after waiting in total {1} ms.",
                    _namePrefix, (System.currentTimeMillis() - startTime));
    }

    /**
     * Wait for new children to arrive until there is a period of length timeout during which
     * no new child arrives, or the method hasResult() returns true. The expectation
     * is that a subclass will monitor incoming updates in its processNewChildren() override
     * method, and in that method, set some sort of flag that will be tested by hasResult().
     * Note that this method does not currently stop enumeration -- enumeration results will
     * continue to accumulate in the background (and request interests will continue to be sent);
     * callers must call stopEnumerating() to actually terminate enumeration.
     */
    public void waitForNoUpdatesOrResult(long timeout) {

        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH,
                    "Waiting for updates on prefix {0} with max timeout of {1} ms between consecutive children arrivals.",
                    _namePrefix, timeout);
        long startTime = System.currentTimeMillis();
        if (hasResult()) {
            return;
        }
        while (waitForNewChildren(false, timeout)) {
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                Log.info(Log.FAC_SEARCH, "Child or children found on prefix {0}. Have result? {1}", _namePrefix,
                        hasResult());
            if (hasResult())
                break;
        }
        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH,
                    "Quit waiting for updates on prefix {0} after waiting in total {1} ms. Have desired result? {2}",
                    _namePrefix, (System.currentTimeMillis() - startTime), hasResult());
    }

    /**
     * Subclasses should override this test to answer true if waiters should break out of a
     * waitForNoUpdatesOrResult loop. Note that results must be cleared manually using clearResult.
     * Default behavior always returns false. Subclasses probably want to set a variable in processNewChildren
     * that will be read here.
     */
    public boolean hasResult() {
        return false;
    }

    /**
     * Reset whatever state hasResult tests. Overridden by subclasses, default does nothing.
     */
    public void clearResult() {
        return;
    }

    /**
     * Method to allow subclasses to do post-processing on incoming names
     * before handing them to customers.
     * Note that the set handed in here is not the set that will be handed
     * out; only the name objects are the same.
     *
     * @param newChildren SortedSet of children available for processing
     *
     * @return void
     */
    protected void processNewChildren(SortedSet<ContentName> newChildren) {
        // default -- do nothing
    }

    /**
     * If some or all of the children of this name are versions, returns the latest version
     * among them.
     *
     * @return ContentName The latest version component
     * */
    public ContentName getLatestVersionChildName() {
        // of the available names in _children that are version components,
        // find the latest one (version-wise)
        // names are sorted, so the last one that is a version should be the latest version
        // ListIterator previous doesn't work unless you've somehow gotten it to point at the end...
        ContentName theName = null;
        ContentName latestName = null;
        CCNTime latestTimestamp = null;
        Iterator<ContentName> it = _children.iterator();
        // TODO these are sorted -- we just need to iterate through them in reverse order. Having
        // trouble finding something like C++'s reverse iterators to do that (linked list iterators
        // can go backwards -- but you have to run them to the end first).
        while (it.hasNext()) {
            theName = it.next();
            if (VersioningProfile.isVersionComponent(theName.component(0))) {
                if (null == latestName) {
                    latestName = theName;
                    latestTimestamp = VersioningProfile.getVersionComponentAsTimestamp(theName.component(0));
                } else {
                    CCNTime thisTimestamp = VersioningProfile.getVersionComponentAsTimestamp(theName.component(0));
                    if (thisTimestamp.after(latestTimestamp)) {
                        latestName = theName;
                        latestTimestamp = thisTimestamp;
                    }
                }
            }
        }
        return latestName;
    }

    /**
     * Handle responses from CCNNameEnumerator that give us a list of single-component child
     * names. Filter out the names new to us, add them to our list of known children, postprocess
     * them with processNewChildren(SortedSet<ContentName>), and signal waiters if we
     * have new data.
     *
     * @param prefix Prefix used for name enumeration.
     * @param names The list of names returned in this name enumeration response.
     *
     * @return int
     */
    @SuppressWarnings("unchecked")
    public int handleNameEnumerator(ContentName prefix, ArrayList<ContentName> names) {

        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
            if (!_enumerating) {
                // Right now, just log if we get data out of enumeration, don't drop it on the floor;
                // don't want to miss results in case we are started again.
                Log.info(Log.FAC_SEARCH,
                        "ENUMERATION STOPPED: but {0} new name enumeration results: our prefix: {1} returned prefix: {2}",
                        names.size(), _namePrefix, prefix);
            } else {
                Log.info(Log.FAC_SEARCH, "{0} new name enumeration results: our prefix: {1} returned prefix: {2}",
                        names.size(), _namePrefix, prefix);
            }
        }
        if (!prefix.equals(_namePrefix)) {
            Log.warning(Log.FAC_SEARCH, "Returned data doesn't match requested prefix!");
        }

        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH, "Handling Name Iteration {0}, size is {1}", prefix, names.size());
        // the name enumerator hands off names to us, we own it now
        // DKS -- want to keep listed as new children we previously had
        synchronized (_childLock) {
            TreeSet<ContentName> thisRoundNew = new TreeSet<ContentName>();
            thisRoundNew.addAll(names);
            Iterator<ContentName> it = thisRoundNew.iterator();
            while (it.hasNext()) {
                ContentName name = it.next();
                if (_children.contains(name)) {
                    it.remove();
                }
            }
            if (!thisRoundNew.isEmpty()) {
                for (NewChildrenByThread ncbt : _newChildrenByThread.values()) {
                    if (null != ncbt._newChildren) {
                        ncbt._newChildren.addAll(thisRoundNew);
                    } else {
                        ncbt._newChildren = (TreeSet<ContentName>) thisRoundNew.clone();
                    }
                }
                _children.addAll(thisRoundNew);
                _lastUpdate = new CCNTime();
                if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
                    Log.info(Log.FAC_SEARCH, "New children found: at {0} {1} total children {2}", _lastUpdate,
                            +thisRoundNew.size(), _children.size());
                }
                processNewChildren(thisRoundNew);
                _childLock.notifyAll();
            }
        }
        return 0;
    }

    /**
     * Returns the latest version available under this prefix as a byte array.
     *
     * @return byte[] Latest child version as byte array
     */
    public byte[] getLatestVersionChildNameComponent() {
        ContentName latestVersionName = getLatestVersionChildName();
        if (null == latestVersionName)
            return null;
        return latestVersionName.component(0);
    }

    /**
     * Returns the latest version available under this prefix as a CCNTime object.
     *
     * @return CCNTime Latest child version as CCNTime
     */
    public CCNTime getLatestVersionChildTime() {
        ContentName latestVersion = getLatestVersionChildName();
        if (null != latestVersion) {
            return VersioningProfile.getVersionComponentAsTimestamp(latestVersion.component(0));
        }
        return null;
    }

    /**
     * A static method that performs a one-shot call that returns the complete name of the latest
     * version of content with the given prefix. An alternative route to finding the name of the
     * latest version of a piece of content, rather than using methods in the VersioningProfile
     * to retrieve an arbitrary block of content under that version. Useful when the data under
     * a version is complex in structure.
     *
     * @param name ContentName to find the latest version of
     * @param handle CCNHandle to use for enumeration
     * @return ContentName The name supplied to the call with the latest version added.
     * @throws IOException
     */
    public static ContentName getLatestVersionName(ContentName name, CCNHandle handle) throws IOException {
        EnumeratedNameList enl = new EnumeratedNameList(name, handle);
        enl.waitForNoUpdates(SystemConfiguration.MAX_TIMEOUT);
        ContentName childLatestVersion = enl.getLatestVersionChildName();
        enl.stopEnumerating();
        if (null != childLatestVersion) {
            return new ContentName(name, childLatestVersion.component(0));
        }
        return null;
    }

    /**
     * Static method that iterates down the namespace starting with the supplied prefix
     * as a ContentName (prefixKnownToExist) to a specific child (childName). The method
     * returns null if the name does not exist in a limited time iteration.  If the child
     * is found, this method returns the EnumeratedNameList object for the parent of the
     * desired child in the namespace.  The current implementation may time out before the
     * desired name is found.  Additionally, the current implementation does not loop on
     * an enumeration attempt, so a child may be missed if it is not included in the first
     * enumeration response.
     *
     * TODO Add loop to enumerate under a name multiple times to avoid missing a child name
     * TODO Handle timeouts better to avoid missing children.  (Note: We could modify the
     * name enumeration protocol to return empty responses if we query for an unknown name,
     *  but that adds semantic complications.)
     *
     * @param childName ContentName for the child we are looking for under (does not have
     * to be directly under) a given prefix.
     * @param prefixKnownToExist ContentName prefix to enumerate to look for a given child.
     * @param handle CCNHandle for sending and receiving interests and content objects.
     *
     * @return EnumeratedNameList Returns the parent EnumeratedNameList for the desired child,
     * if one is found.  Returns null if the child is not found.
     *
     * @throws IOException
     */
    public static EnumeratedNameList exists(ContentName childName, ContentName prefixKnownToExist, CCNHandle handle)
            throws IOException {
        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH,
                    "EnumeratedNameList.exists: the prefix known to exist is {0} and we are looking for childName {1}",
                    prefixKnownToExist, childName);
        if ((null == prefixKnownToExist) || (null == childName) || (!prefixKnownToExist.isPrefixOf(childName))) {
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: Child {0} must be prefixed by name {1}",
                        childName, prefixKnownToExist);
            throw new IllegalArgumentException(
                    "Child " + childName + " must be prefixed by name " + prefixKnownToExist);
        }
        if (childName.count() == prefixKnownToExist.count()) {
            // we're already there
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: we're already there.");
            return new EnumeratedNameList(childName, handle);
        }
        ContentName parentName = prefixKnownToExist;
        int childIndex = parentName.count();
        EnumeratedNameList parentEnumerator = null;
        while (childIndex < childName.count()) {
            byte[] childNameComponent = childName.component(childIndex);
            parentEnumerator = new EnumeratedNameList(parentName, handle);
            if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: enumerating the parent name {0}", parentName);
            parentEnumerator.waitForChildren(SystemConfiguration.MAX_TIMEOUT);
            while (!parentEnumerator.hasChild(childNameComponent)) {
                if (!parentEnumerator.waitForNewChildren(false, SystemConfiguration.MAX_TIMEOUT))
                    break;
            }
            if (parentEnumerator.hasChild(childNameComponent)) {
                if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) {
                    Log.info(Log.FAC_SEARCH,
                            "EnumeratedNameList.exists: we have a matching child to {0} and the parent enumerator {1} has {2} children.",
                            Component.printURI(childNameComponent), parentName, parentEnumerator.childCount());
                }
                childIndex++;
                if (childIndex == childName.count()) {
                    if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
                        Log.info(Log.FAC_SEARCH,
                                "EnumeratedNameList.exists: we found the childName we were looking for: {0}",
                                childName);
                    return parentEnumerator;
                }
                parentEnumerator.stopEnumerating();
                parentName = new ContentName(parentName, childNameComponent);
                continue;
            } else {
                if (Log.isLoggable(Level.INFO)) {
                    Log.info(
                            "EnumeratedNameList.exists: the parent enumerator {0} has {1} children but none of them are {2}.",
                            parentName, parentEnumerator.childCount(), Component.printURI(childNameComponent));
                }
                break;
            }
        }
        if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO))
            Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: returning null for search of {0}", childName);
        return null;
    }

    private NewChildrenByThread getNcbt() {
        Long id = Thread.currentThread().getId();
        NewChildrenByThread ncbt = _newChildrenByThread.get(id);
        if (null == ncbt)
            ncbt = _newChildrenByThread.get(0L); // Thread pool
        return ncbt;
    }

    /**
     * Enables an EnumeratedNameList to be used directly in a ContentName builder.
     * @return Gets the ContentName of the prefix being enumerated
     * @see ContentNameProvider
     * @see ContentName#builder(org.ccnx.ccn.protocol.ContentName.StringParser, Object[])
     */
    public ContentName getContentName() {
        return _namePrefix;
    }
}