<commit_before>/*
 * A CCNx library test.
 *
 * Copyright (C) 2011 Palo Alto Research Center, Inc.
 *
 * This work is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation. 
 * This work 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 General Public License
 * for more details. You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
package org.ccnx.ccn.test.io.content;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Random;
import java.util.TreeSet;
import java.util.logging.Level;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import junit.framework.Assert;

import org.ccnx.ccn.CCNFilterListener;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.impl.CCNNetworkManager;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.keys.BasicKeyManager;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.CCNStringObject;
import org.ccnx.ccn.io.content.LocalCopyListener;
import org.ccnx.ccn.io.content.LocalCopyWrapper;
import org.ccnx.ccn.profiles.ccnd.CCNDaemonException;
import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager;
import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager.ForwardingEntry;
import org.ccnx.ccn.profiles.repo.RepositoryControl;
import org.ccnx.ccn.profiles.repo.RepositoryOperations;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Test RepositoryControl, LocalCopyListener, and LocalCopyWrapper to make sure
 * there are no dangling faces in ccnd when they stop.
 * 
 * In general, the tests go like this:
 * - Create an Interest listener for our object
 * - Ask the repo to sync it
 * - Create the object and save it in the Interest handler.
 * - At each step, use the ccnd xml export to verify that the proper number of
 *   prefixes are registered.
 */
public class LocalCopyTestRepo {
	
	CCNHandle readhandle;
	CCNHandle listenerhandle;
	int readFaceId;
	int listenerFaceId;
	BasicKeyManager km;
	final static Random _rnd = new Random();
	final static String _prefix = String.format("/test_%016X", _rnd.nextLong());
	
	final static int LONG_TIMEOUT = 3000;
	final static int SHORT_TIMEOUT = 500;
	
	static int _port = CCNNetworkManager.DEFAULT_AGENT_PORT;
	static String _host = CCNNetworkManager.DEFAULT_AGENT_HOST;
	static String _ccndurl;
	
	// by faceid
    final HashMap<Integer,TreeSet<ContentName>> fentries = new HashMap<Integer, TreeSet<ContentName>>();

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
		// Determine port at which to contact agent
		String portval = System.getProperty(CCNNetworkManager.PROP_AGENT_PORT);
		if (null != portval) {
			try {
				_port = new Integer(portval);
			} catch (Exception ex) {
				throw new IOException("Invalid port '" + portval + "' specified in " + CCNNetworkManager.PROP_AGENT_PORT);
			}
			Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent port " + _port + " per property " + CCNNetworkManager.PROP_AGENT_PORT);
		}
		
		String hostval = System.getProperty(CCNNetworkManager.PROP_AGENT_HOST);
		if (null != hostval && hostval.length() > 0) {
			_host = hostval;
			Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent host " + _host + " per property " + CCNNetworkManager.PROP_AGENT_HOST);
		}
		
		_ccndurl = String.format("http://%s:%d/?f=xml", _host, _port);
		System.out.println("Using ccnd url: " + _ccndurl);
    }
    
 	@Before
	public void setUp() throws Exception {
		System.out.println("*****************************************************");
		Log.setLevel(Log.FAC_ALL, Level.WARNING);
		
		km = new BasicKeyManager();
		km.initialize();
		KeyManager.setDefaultKeyManager(km);
		readhandle = CCNHandle.open(km);
		listenerhandle = CCNHandle.open(km);
		
		// Setup a prefix to get my face
		readFaceId = getFaceId(readhandle, "read");
		listenerFaceId = getFaceId(listenerhandle, "listener");
		
		System.out.println(String.format("Face IDs: read %d, listen %d", readFaceId, listenerFaceId));
		
	}
	
	@After
	public void tearDown() throws Exception {
		listenerhandle.close();
		readhandle.close();
		KeyManager.closeDefaultKeyManager();
	}
	
//	@Test
//	public void testDumpFaces() throws Exception {
//		getfaces();
//	}
	
	@Test
	public void testRepositoryControlObject() throws Exception {
		System.out.println(">>> Running testRepositoryControlObject");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			
			RepositoryControl.localRepoSync(readhandle, so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After localRepoSync on string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}

	@Test
	public void testLocalCopyWrapper() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapper");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			lcw.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyListener() throws Exception {
		System.out.println(">>> Running testLocalCopyListener");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyListener.startBackup(so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyListener.startBackup");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
						
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyWrapperWithSaveAndLcwClose() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapperWithSaveAndLcwClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			lcw.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			lcw.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyWrapperWithSaveAndObjectClose() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapperWithSaveAndObjectClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			lcw.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// IMPORTANT: This in an incorrect usage, as we're closing the underlying
			// object, not the localcopywrapper.
			so_in.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			// IMPORTANT: Notice that the readFaceId still has 2 registrations.
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyListnerWithSaveAndObjectClose() throws Exception {
		System.out.println(">>> Running testLocalCopyListnerWithSaveAndObjectClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyListener.startBackup(so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			so_in.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			so_in.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	// ==============================================================================================
	/**
	 * Interest listener to create random objects for the repo to sync
	 * @author mmosko
	 *
	 */
	private class MyListener implements CCNFilterListener {
		// These are the replies we have sent
//		public ConcurrentHashMap<ContentName, String> replies = new ConcurrentHashMap<ContentName, String>();
		public HashSet<ContentName> replies = new HashSet<ContentName>();
		
		public void open() throws MalformedContentNameStringException, IOException, InterruptedException {
			listenerhandle.registerFilter(ContentName.fromNative(_prefix), this);
			Thread.sleep(100);
		}
		
		public void close() throws MalformedContentNameStringException {
			listenerhandle.unregisterFilter(ContentName.fromNative(_prefix), this);
		}
		
		@Override
		public boolean handleInterest(Interest interest) {
			// Ignore start write requests
			
			if( RepositoryOperations.isStartWriteOperation(interest) ) 
				return false;
			
			if( RepositoryOperations.isCheckedWriteOperation(interest) ) 
				return false;

			synchronized(replies) {
				if( replies.contains(interest.name()))
					return false;
				
				System.out.println("handleInterest: " + interest.toString());
				
				try {
					String s = interest.name().toString();
					CCNStringObject so = new CCNStringObject(interest.name(), s, SaveType.RAW, listenerhandle);
					so.save();
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
					}
					so.close();
					replies.add(interest.name());
					return true;
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				return false;
			}
		}
	}
	
	/*
	 <ccnd>
	   <identity><ccndid>0E0BBF5633A562DDD2D354150FBB6D0055042A389F10700D5C010DA621B6586E</ccndid>
	   <apiversion>3002</apiversion>
	   <starttime>1303848970.212333</starttime>
	   <now>1303852704.538848</now>
	   </identity>
	   <cobs>
	      <accessioned>302</accessioned>
	      <stored>302</stored>
	      <stale>47</stale>
	      <sparse>0</sparse>
	      <duplicate>0</duplicate>
	      <sent>352</sent>
	   </cobs>
	   <interests>
	      <names>15</names>
	      <pending>0</pending>
	      <propagating>0</propagating>
	      <noted>0</noted>
	      <accepted>376</accepted>
	      <dropped>0</dropped>
	      <sent>600</sent>
	      <stuffed>0</stuffed>
	   </interests>
	   <faces>
	      <face>
	         <faceid>0</faceid>
	         <faceflags>000c</faceflags>
	         <pending>0</pending>
	         <recvcount>0</recvcount>
	         <meters>
	            <bytein><total>45140</total><persec>0</persec></bytein><byteout><total>26665</total><persec>0</persec></byteout><datain><total>46</total><persec>0</persec></datain><introut><total>51</total><persec>0</persec></introut><dataout><total>0</total><persec>0</persec></dataout><intrin><total>0</total><persec>0</persec></intrin>
	         </meters>
	      </face>
		<face><faceid>1</faceid><faceflags>400c</faceflags><pending>0</pending><recvcount>0</recvcount></face>
		<face><faceid>2</faceid><faceflags>5012</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face>
		<face><faceid>3</faceid><faceflags>5010</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face>
		<face><faceid>4</faceid><faceflags>4042</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face>
		<face><faceid>5</faceid><faceflags>4040</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face>
		<face>
		   <faceid>6</faceid>
		   <faceflags>1014</faceflags>
		   <pending>0</pending>
		   <recvcount>6</recvcount>
		   <ip>127.0.0.1:57037</ip>
		   <meters><bytein><total>1949</total><persec>0</persec></bytein><byteout><total>4006</total><persec>0</persec></byteout><datain><total>1</total><persec>0</persec></datain><introut><total>1</total><persec>0</persec></introut><dataout><total>5</total><persec>0</persec></dataout><intrin><total>5</total><persec>0</persec></intrin>
		   </meters>
		</face>
		<face><faceid>7</faceid><faceflags>1014</faceflags><pending>0</pending><recvcount>14</recvcount><ip>127.0.0.1:57040</ip><meters><bytein><total>4121</total><persec>0</persec></bytein><byteout><total>36582</total><persec>0</persec></byteout><datain><total>5</total><persec>0</persec></datain><introut><total>273</total><persec>0</persec></introut><dataout><total>7</total><persec>0</persec></dataout><intrin><total>9</total><persec>0</persec></intrin>
		</meters></face>
	</faces>
	<forwarding>
		<fentry>
			<prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/ccnd</prefix>
			<dest><faceid>0</faceid>
				<flags>3</flags>
				<expires>2147479917</expires>
			</dest>
		</fentry>
		<fentry><prefix>ccnx:/ccnx/ping</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx/%0E%0B%BFV3%A5b%DD%D2%D3T%15%0F%BBm%00U%04%2A8%9F%10p%0D%5C%01%0D%A6%21%B6Xn</prefix><dest><faceid>0</faceid><flags>17</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx.org/Users/Repository/Keys/%C1.M.K%00%00%96%96c1%3D%A5%D5%E4%C8%29B%08%B1t%80D%1D%2A%BC3%BA%9A6%90N%09%8D%A2t%27%24</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry><fentry><prefix>ccnx:/</prefix><dest><faceid>7</faceid><flags>43</flags><expires>2147479927</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.neighborhood</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost</prefix><dest><faceid>0</faceid><flags>23</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/repository/KEY</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry>
	</forwarding>
</ccnd>


	 */
	
	private void getfaces() throws ParserConfigurationException, SAXException, IOException, MalformedContentNameStringException, DOMException {
        javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setValidating(false);
        javax.xml.parsers.DocumentBuilder parser = dbf.newDocumentBuilder();
        org.w3c.dom.Document document;
        
        synchronized(fentries) {
        	fentries.clear();
        	
	        document = parser.parse(_ccndurl);
	        NodeList nodes = document.getElementsByTagName("fentry");
	        System.out.println("fentry count: " + nodes.getLength());
	        
	        for(int i = 0; i< nodes.getLength(); i++) {
//	        	System.out.println("Parsing entry " + i);
	        	Node node = nodes.item(i);    	
	        	FEntry fentry = new FEntry(node);
	        	
	        	for(Dest dest : fentry.dests) {
	        		TreeSet<ContentName> regs = fentries.get(dest.faceid);
	        		if( null == regs ) {
	        			regs = new TreeSet<ContentName>();
	        			fentries.put(dest.faceid, regs);
	        		}
		        	regs.add(fentry.entryprefix);
	        	}
	        }
	        
//	        for(Integer faceid : fentries.keySet() ) {
//	        	dumpreg(faceid);
//	        }
        }
	}
	
	private static class Dest {
		int faceid = -1;
		int flags = 0;
		long expires = -1;
		
		public Dest(Node node) {
			if( node.getNodeName() != "dest" )
				throw new ClassCastException("node is not of type 'dest': " + node.getNodeName());
			NodeList children = node.getChildNodes();
			for(int i = 0; i < children.getLength(); i++) {
				Node child = children.item(i);
				
				if( child.getNodeName() == "faceid" ) 
					faceid = Integer.parseInt(child.getFirstChild().getNodeValue());
				
				if( child.getNodeName() == "flags" )
					flags = Integer.parseInt(child.getFirstChild().getNodeValue());

				if( child.getNodeName() == "expires" )
					expires = Long.parseLong(child.getFirstChild().getNodeValue());

			}
		}
		
		public String toString() {
			return String.format("faceid %d flags %08X expires %d", faceid, flags, expires);
		}
	}
	
	private static class FEntry {
		ContentName entryprefix = null;
		LinkedList<Dest> dests = new LinkedList<Dest>();
		
		public FEntry(final Node node) throws MalformedContentNameStringException, DOMException {
			if( node.getNodeName() != "fentry" )
				throw new ClassCastException("node is not of type 'fentry'");
			
			NodeList children = node.getChildNodes();
			for(int i = 0; i < children.getLength(); i++) {
				Node child = children.item(i);
				if( child.getNodeName() == "dest" ) {
					Dest dest = new Dest(child);
//					System.out.println("adding: " + dest.toString());
					dests.add(dest);
				}
				
				if( child.getNodeName() == "prefix" ) {
					entryprefix = ContentName.fromURI(child.getFirstChild().getNodeValue());
//					System.out.println("prefix " + entryprefix.toURIString());
				}
			}
		}

		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("prefix: ");
			sb.append(entryprefix.toURIString());
			sb.append('\n');
			for(Dest dest : dests) {
				sb.append("  faceid: ");
				sb.append(dest.faceid);
				sb.append('\n');
			}
			return sb.toString();
		}
		
	}
	
	private int getFaceId(CCNHandle handle, String type) throws CCNDaemonException {
		PrefixRegistrationManager prm = new PrefixRegistrationManager(handle);
		String reg = String.format("ccnx:%s/%s_%016X", _prefix, type, _rnd.nextLong());
		ForwardingEntry entry = prm.selfRegisterPrefix(reg);
		return entry.getFaceID();
	}
	
	/**
	 * 
	 * @param faceid
	 * @return the # of prefixes registered on the face
	 */
	private int dumpreg(int faceid) {
		int count = 0;
		synchronized(fentries) {
			TreeSet<ContentName> regs = fentries.get(faceid);
			System.out.println("Registrations for faceid " + faceid);
			for(ContentName name : regs) {
				System.out.println("   " + name.toURIString());
				count++;
			}
		}
		return count;
	}
}
<commit_msg>Refs #100464.  Shortened timeout to make test finish faster.<commit_after>/*
 * A CCNx library test.
 *
 * Copyright (C) 2011 Palo Alto Research Center, Inc.
 *
 * This work is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation. 
 * This work 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 General Public License
 * for more details. You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
package org.ccnx.ccn.test.io.content;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Random;
import java.util.TreeSet;
import java.util.logging.Level;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import junit.framework.Assert;

import org.ccnx.ccn.CCNFilterListener;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.impl.CCNNetworkManager;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.keys.BasicKeyManager;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.CCNStringObject;
import org.ccnx.ccn.io.content.LocalCopyListener;
import org.ccnx.ccn.io.content.LocalCopyWrapper;
import org.ccnx.ccn.profiles.ccnd.CCNDaemonException;
import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager;
import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager.ForwardingEntry;
import org.ccnx.ccn.profiles.repo.RepositoryControl;
import org.ccnx.ccn.profiles.repo.RepositoryOperations;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Test RepositoryControl, LocalCopyListener, and LocalCopyWrapper to make sure
 * there are no dangling faces in ccnd when they stop.
 * 
 * In general, the tests go like this:
 * - Create an Interest listener for our object
 * - Ask the repo to sync it
 * - Create the object and save it in the Interest handler.
 * - At each step, use the ccnd xml export to verify that the proper number of
 *   prefixes are registered.
 */
public class LocalCopyTestRepo {
	
	CCNHandle readhandle;
	CCNHandle listenerhandle;
	int readFaceId;
	int listenerFaceId;
	BasicKeyManager km;
	final static Random _rnd = new Random();
	final static String _prefix = String.format("/test_%016X", _rnd.nextLong());
	
	final static int LONG_TIMEOUT = 1000;
	final static int SHORT_TIMEOUT = 500;
	
	static int _port = CCNNetworkManager.DEFAULT_AGENT_PORT;
	static String _host = CCNNetworkManager.DEFAULT_AGENT_HOST;
	static String _ccndurl;
	
	// by faceid
    final HashMap<Integer,TreeSet<ContentName>> fentries = new HashMap<Integer, TreeSet<ContentName>>();

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
		// Determine port at which to contact agent
		String portval = System.getProperty(CCNNetworkManager.PROP_AGENT_PORT);
		if (null != portval) {
			try {
				_port = new Integer(portval);
			} catch (Exception ex) {
				throw new IOException("Invalid port '" + portval + "' specified in " + CCNNetworkManager.PROP_AGENT_PORT);
			}
			Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent port " + _port + " per property " + CCNNetworkManager.PROP_AGENT_PORT);
		}
		
		String hostval = System.getProperty(CCNNetworkManager.PROP_AGENT_HOST);
		if (null != hostval && hostval.length() > 0) {
			_host = hostval;
			Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent host " + _host + " per property " + CCNNetworkManager.PROP_AGENT_HOST);
		}
		
		_ccndurl = String.format("http://%s:%d/?f=xml", _host, _port);
		System.out.println("Using ccnd url: " + _ccndurl);
    }
    
 	@Before
	public void setUp() throws Exception {
		System.out.println("*****************************************************");
		Log.setLevel(Log.FAC_ALL, Level.WARNING);
		
		km = new BasicKeyManager();
		km.initialize();
		KeyManager.setDefaultKeyManager(km);
		readhandle = CCNHandle.open(km);
		listenerhandle = CCNHandle.open(km);
		
		// Setup a prefix to get my face
		readFaceId = getFaceId(readhandle, "read");
		listenerFaceId = getFaceId(listenerhandle, "listener");
		
		System.out.println(String.format("Face IDs: read %d, listen %d", readFaceId, listenerFaceId));
		
	}
	
	@After
	public void tearDown() throws Exception {
		listenerhandle.close();
		readhandle.close();
		KeyManager.closeDefaultKeyManager();
	}
	
//	@Test
//	public void testDumpFaces() throws Exception {
//		getfaces();
//	}
	
	@Test
	public void testRepositoryControlObject() throws Exception {
		System.out.println(">>> Running testRepositoryControlObject");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			
			RepositoryControl.localRepoSync(readhandle, so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After localRepoSync on string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}

	@Test
	public void testLocalCopyWrapper() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapper");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			lcw.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyListener() throws Exception {
		System.out.println(">>> Running testLocalCopyListener");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyListener.startBackup(so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyListener.startBackup");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
						
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyWrapperWithSaveAndLcwClose() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapperWithSaveAndLcwClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			lcw.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			lcw.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyWrapperWithSaveAndObjectClose() throws Exception {
		System.out.println(">>> Running testLocalCopyWrapperWithSaveAndObjectClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyWrapper lcw = new LocalCopyWrapper(so_in);
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			lcw.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// IMPORTANT: This in an incorrect usage, as we're closing the underlying
			// object, not the localcopywrapper.
			so_in.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			// IMPORTANT: Notice that the readFaceId still has 2 registrations.
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	@Test
	public void testLocalCopyListnerWithSaveAndObjectClose() throws Exception {
		System.out.println(">>> Running testLocalCopyListnerWithSaveAndObjectClose");
		MyListener listener = new MyListener();
		
		try {
			listener.open();
			
			String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong());
			ContentName name = ContentName.fromNative(namestring);
			
			CCNStringObject so_in = new CCNStringObject(name, readhandle);
			so_in.setupSave(SaveType.LOCALREPOSITORY);
			
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After reading string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
			LocalCopyListener.startBackup(so_in);
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper on string object");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			// Now modify the string object and save again.
			so_in.setData(String.format("%016X", _rnd.nextLong()));
			so_in.save();
			Thread.sleep(LONG_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper save");
			getfaces();
			Assert.assertEquals(2, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));

			so_in.close();
			Thread.sleep(SHORT_TIMEOUT);
			System.out.println("======= After LocalCopyWrapper close");
			getfaces();
			Assert.assertEquals(1, dumpreg(readFaceId));
			Assert.assertEquals(2, dumpreg(listenerFaceId));
			
		} finally {
			listener.close();
		}
	}
	
	// ==============================================================================================
	/**
	 * Interest listener to create random objects for the repo to sync
	 * @author mmosko
	 *
	 */
	private class MyListener implements CCNFilterListener {
		// These are the replies we have sent
//		public ConcurrentHashMap<ContentName, String> replies = new ConcurrentHashMap<ContentName, String>();
		public HashSet<ContentName> replies = new HashSet<ContentName>();
		
		public void open() throws MalformedContentNameStringException, IOException, InterruptedException {
			listenerhandle.registerFilter(ContentName.fromNative(_prefix), this);
			Thread.sleep(100);
		}
		
		public void close() throws MalformedContentNameStringException {
			listenerhandle.unregisterFilter(ContentName.fromNative(_prefix), this);
		}
		
		@Override
		public boolean handleInterest(Interest interest) {
			// Ignore start write requests
			
			if( RepositoryOperations.isStartWriteOperation(interest) ) 
				return false;
			
			if( RepositoryOperations.isCheckedWriteOperation(interest) ) 
				return false;

			synchronized(replies) {
				if( replies.contains(interest.name()))
					return false;
				
				System.out.println("handleInterest: " + interest.toString());
				
				try {
					String s = interest.name().toString();
					CCNStringObject so = new CCNStringObject(interest.name(), s, SaveType.RAW, listenerhandle);
					so.save();
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
					}
					so.close();
					replies.add(interest.name());
					return true;
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				return false;
			}
		}
	}
	
	/*
	 <ccnd>
	   <identity><ccndid>0E0BBF5633A562DDD2D354150FBB6D0055042A389F10700D5C010DA621B6586E</ccndid>
	   <apiversion>3002</apiversion>
	   <starttime>1303848970.212333</starttime>
	   <now>1303852704.538848</now>
	   </identity>
	   <cobs>
	      <accessioned>302</accessioned>
	      <stored>302</stored>
	      <stale>47</stale>
	      <sparse>0</sparse>
	      <duplicate>0</duplicate>
	      <sent>352</sent>
	   </cobs>
	   <interests>
	      <names>15</names>
	      <pending>0</pending>
	      <propagating>0</propagating>
	      <noted>0</noted>
	      <accepted>376</accepted>
	      <dropped>0</dropped>
	      <sent>600</sent>
	      <stuffed>0</stuffed>
	   </interests>
	   <faces>
	      <face>
	         <faceid>0</faceid>
	         <faceflags>000c</faceflags>
	         <pending>0</pending>
	         <recvcount>0</recvcount>
	         <meters>
	            <bytein><total>45140</total><persec>0</persec></bytein><byteout><total>26665</total><persec>0</persec></byteout><datain><total>46</total><persec>0</persec></datain><introut><total>51</total><persec>0</persec></introut><dataout><total>0</total><persec>0</persec></dataout><intrin><total>0</total><persec>0</persec></intrin>
	         </meters>
	      </face>
		<face><faceid>1</faceid><faceflags>400c</faceflags><pending>0</pending><recvcount>0</recvcount></face>
		<face><faceid>2</faceid><faceflags>5012</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face>
		<face><faceid>3</faceid><faceflags>5010</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face>
		<face><faceid>4</faceid><faceflags>4042</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face>
		<face><faceid>5</faceid><faceflags>4040</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face>
		<face>
		   <faceid>6</faceid>
		   <faceflags>1014</faceflags>
		   <pending>0</pending>
		   <recvcount>6</recvcount>
		   <ip>127.0.0.1:57037</ip>
		   <meters><bytein><total>1949</total><persec>0</persec></bytein><byteout><total>4006</total><persec>0</persec></byteout><datain><total>1</total><persec>0</persec></datain><introut><total>1</total><persec>0</persec></introut><dataout><total>5</total><persec>0</persec></dataout><intrin><total>5</total><persec>0</persec></intrin>
		   </meters>
		</face>
		<face><faceid>7</faceid><faceflags>1014</faceflags><pending>0</pending><recvcount>14</recvcount><ip>127.0.0.1:57040</ip><meters><bytein><total>4121</total><persec>0</persec></bytein><byteout><total>36582</total><persec>0</persec></byteout><datain><total>5</total><persec>0</persec></datain><introut><total>273</total><persec>0</persec></introut><dataout><total>7</total><persec>0</persec></dataout><intrin><total>9</total><persec>0</persec></intrin>
		</meters></face>
	</faces>
	<forwarding>
		<fentry>
			<prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/ccnd</prefix>
			<dest><faceid>0</faceid>
				<flags>3</flags>
				<expires>2147479917</expires>
			</dest>
		</fentry>
		<fentry><prefix>ccnx:/ccnx/ping</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx/%0E%0B%BFV3%A5b%DD%D2%D3T%15%0F%BBm%00U%04%2A8%9F%10p%0D%5C%01%0D%A6%21%B6Xn</prefix><dest><faceid>0</faceid><flags>17</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx.org/Users/Repository/Keys/%C1.M.K%00%00%96%96c1%3D%A5%D5%E4%C8%29B%08%B1t%80D%1D%2A%BC3%BA%9A6%90N%09%8D%A2t%27%24</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry><fentry><prefix>ccnx:/</prefix><dest><faceid>7</faceid><flags>43</flags><expires>2147479927</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.neighborhood</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost</prefix><dest><faceid>0</faceid><flags>23</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/repository/KEY</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry>
	</forwarding>
</ccnd>


	 */
	
	private void getfaces() throws ParserConfigurationException, SAXException, IOException, MalformedContentNameStringException, DOMException {
        javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setValidating(false);
        javax.xml.parsers.DocumentBuilder parser = dbf.newDocumentBuilder();
        org.w3c.dom.Document document;
        
        synchronized(fentries) {
        	fentries.clear();
        	
	        document = parser.parse(_ccndurl);
	        NodeList nodes = document.getElementsByTagName("fentry");
	        System.out.println("fentry count: " + nodes.getLength());
	        
	        for(int i = 0; i< nodes.getLength(); i++) {
//	        	System.out.println("Parsing entry " + i);
	        	Node node = nodes.item(i);    	
	        	FEntry fentry = new FEntry(node);
	        	
	        	for(Dest dest : fentry.dests) {
	        		TreeSet<ContentName> regs = fentries.get(dest.faceid);
	        		if( null == regs ) {
	        			regs = new TreeSet<ContentName>();
	        			fentries.put(dest.faceid, regs);
	        		}
		        	regs.add(fentry.entryprefix);
	        	}
	        }
	        
//	        for(Integer faceid : fentries.keySet() ) {
//	        	dumpreg(faceid);
//	        }
        }
	}
	
	private static class Dest {
		int faceid = -1;
		int flags = 0;
		long expires = -1;
		
		public Dest(Node node) {
			if( node.getNodeName() != "dest" )
				throw new ClassCastException("node is not of type 'dest': " + node.getNodeName());
			NodeList children = node.getChildNodes();
			for(int i = 0; i < children.getLength(); i++) {
				Node child = children.item(i);
				
				if( child.getNodeName() == "faceid" ) 
					faceid = Integer.parseInt(child.getFirstChild().getNodeValue());
				
				if( child.getNodeName() == "flags" )
					flags = Integer.parseInt(child.getFirstChild().getNodeValue());

				if( child.getNodeName() == "expires" )
					expires = Long.parseLong(child.getFirstChild().getNodeValue());

			}
		}
		
		public String toString() {
			return String.format("faceid %d flags %08X expires %d", faceid, flags, expires);
		}
	}
	
	private static class FEntry {
		ContentName entryprefix = null;
		LinkedList<Dest> dests = new LinkedList<Dest>();
		
		public FEntry(final Node node) throws MalformedContentNameStringException, DOMException {
			if( node.getNodeName() != "fentry" )
				throw new ClassCastException("node is not of type 'fentry'");
			
			NodeList children = node.getChildNodes();
			for(int i = 0; i < children.getLength(); i++) {
				Node child = children.item(i);
				if( child.getNodeName() == "dest" ) {
					Dest dest = new Dest(child);
//					System.out.println("adding: " + dest.toString());
					dests.add(dest);
				}
				
				if( child.getNodeName() == "prefix" ) {
					entryprefix = ContentName.fromURI(child.getFirstChild().getNodeValue());
//					System.out.println("prefix " + entryprefix.toURIString());
				}
			}
		}

		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("prefix: ");
			sb.append(entryprefix.toURIString());
			sb.append('\n');
			for(Dest dest : dests) {
				sb.append("  faceid: ");
				sb.append(dest.faceid);
				sb.append('\n');
			}
			return sb.toString();
		}
		
	}
	
	private int getFaceId(CCNHandle handle, String type) throws CCNDaemonException {
		PrefixRegistrationManager prm = new PrefixRegistrationManager(handle);
		String reg = String.format("ccnx:%s/%s_%016X", _prefix, type, _rnd.nextLong());
		ForwardingEntry entry = prm.selfRegisterPrefix(reg);
		return entry.getFaceID();
	}
	
	/**
	 * 
	 * @param faceid
	 * @return the # of prefixes registered on the face
	 */
	private int dumpreg(int faceid) {
		int count = 0;
		synchronized(fentries) {
			TreeSet<ContentName> regs = fentries.get(faceid);
			System.out.println("Registrations for faceid " + faceid);
			for(ContentName name : regs) {
				System.out.println("   " + name.toURIString());
				count++;
			}
		}
		return count;
	}
}

{'fraction_non_alphanumeric': 0.15363987180325725, 'fraction_numerical': 0.024113196851767067, 'mean_word_length': 4.27097219030108, 'pattern_counts': {'":': 0, '<': 735, '<?xml version=': 0, '>': 767, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 14}, 'pii_count': 8, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6345164', 'n_tokens_mistral': 17125, 'n_tokens_neox': 14812, 'n_words': 2670}