/* * Part of the CCNx Java Library. * * Copyright (C) 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.impl.sync; import java.util.ArrayList; import java.util.Collection; import java.util.TreeSet; import java.util.logging.Level; import org.ccnx.ccn.CCNSync; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.io.content.SyncNodeComposite; import org.ccnx.ccn.io.content.SyncNodeComposite.SyncNodeElement; import org.ccnx.ccn.io.content.SyncNodeComposite.SyncNodeType; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; /** * Code to build Sync nodes */ public class NodeBuilder { public abstract class NodeCommon<X> { public SyncTreeEntry createNodeCommon(Collection<X> objects, int depth, SyncHashCache shc, SyncNodeCache cache) { ArrayList<SyncNodeComposite.SyncNodeElement> refs = new ArrayList<SyncNodeComposite.SyncNodeElement>(); int total = 0; int limit = CCNSync.NODE_SPLIT_TRIGGER - CCNSync.NODE_SPLIT_TRIGGER/8; int minLen = CCNSync.NODE_SPLIT_TRIGGER/2; int maxLen = 0; int prevTotal = 0; int split = 0; X tobj = null; for (X ne : objects) { XMLEncodable nextE = (XMLEncodable)ne; if (null != tobj) { byte[] lengthTest; try { lengthTest = nextE.encode(); int nameLen = lengthTest.length + 8; if (nameLen > maxLen) maxLen = nameLen; total += (nameLen + ((maxLen - nameLen) * 2)); } catch (ContentEncodingException e) {} // Shouldn't happen because we built the data prevTotal = extraSplit(ne, tobj, total, minLen, prevTotal); if (prevTotal < 0) break; if (total > limit) break; } tobj = ne; split = split + 1; } int i = 0; ArrayList<X> removes = new ArrayList<X>(); for (X thisOne : objects) { SyncNodeElement sne = newElement(thisOne); refs.add(sne); removes.add(thisOne); if (++i >= split) break; } objects.removeAll(removes); SyncNodeComposite snc = newNode(refs, depth); if (null == snc) { Log.warning(Log.FAC_SYNC, "Couldn't build node - shouldn't happen"); return null; } SyncTreeEntry ste = new SyncTreeEntry(snc.getHash(), cache); shc.putHashEntry(ste); ste.setLocal(true); ste.setNode(snc); return ste; } public abstract int extraSplit(X ne, X te, int total, int minLen, int prevTotal); public abstract SyncNodeElement newElement(X xe); public abstract SyncNodeComposite newNode(ArrayList<SyncNodeComposite.SyncNodeElement> refs, int depth); } /** * Create a new leaf node from the names entered. We use only as many names as will fit into * the node, and remove those names from the input list so that after this has completed, the * list contains the remaining names which are not yet in a node. * * @param names - the set of names in canonical order * @return */ public SyncTreeEntry newLeafNode(TreeSet<ContentName> names, SyncHashCache shc, SyncNodeCache cache) { SyncTreeEntry ste = new NodeCommon<ContentName>() { public int extraSplit(ContentName nextName, ContentName tname, int total, int minLen, int prevMatch) { int match = tname.matchLength(nextName); if (total > minLen) { if (match < prevMatch || match > + 1) { if (Log.isLoggable(Log.FAC_SYNC, Level.FINE)) { Log.fine(Log.FAC_SYNC, "Node split due to level change - nbytes {0}, match {1}, prev {2}", total, match, prevMatch); } return -1; } byte[] lc = tname.lastComponent(); if (lc.length > 8) { int c = (int)(lc[lc.length - 7] & 255); if (c < CCNSync.HASH_SPLIT_TRIGGER) { if (Log.isLoggable(Log.FAC_SYNC, Level.FINE)) { Log.fine(Log.FAC_SYNC, "Node split due to hash split - nbytes {0}, val {1}", total, c); } return -1; } } } return match; } public SyncNodeElement newElement(ContentName name) { return new SyncNodeComposite.SyncNodeElement(name); } public SyncNodeComposite newNode(ArrayList<SyncNodeElement> refs,int depth) { return new SyncNodeComposite(refs, refs.get(0), refs.get(refs.size() - 1), refs.size(), depth); } }.createNodeCommon(names, 1, shc, cache); SyncNodeComposite theNode = ste.getNode(); if (null != theNode) { theNode.setLeafCount(theNode.getRefs().size()); } return ste; } /** * Create a node of nodes from the input map. We use only as many nodes as will fit into the * new node and remove those from the list. * * @param nodes * @return */ public SyncTreeEntry newNodeOfNodes(Collection<SyncNodeElement> nodes, final SyncHashCache shc, SyncNodeCache cache, int depth) { SyncTreeEntry ste = new NodeCommon<SyncNodeElement>() { public int extraSplit(SyncNodeElement n, SyncNodeElement tname, int total, int minLen, int prevMatch) { return 0; } public SyncNodeElement newElement(SyncNodeElement element) { return new SyncNodeElement(element.getData()); } public SyncNodeComposite newNode(ArrayList<SyncNodeElement> refs, int depth) { SyncNodeElement first = findit(refs, shc, true); if (null == first) { Log.warning(Log.FAC_SYNC, "Can't get hash or node for {0} in newNode - shouldn't happen", Component.printURI(refs.get(0).getData())); return null; } SyncNodeElement last = findit(refs, shc, false); if (null == last) { Log.warning(Log.FAC_SYNC, "Can't get hash or node for {0} in newNode - shouldn't happen", Component.printURI(refs.get(refs.size() - 1).getData())); return null; } return new SyncNodeComposite(refs, first, last, refs.size(), depth); } }.createNodeCommon(nodes, depth, shc, cache); SyncNodeComposite theNode = ste.getNode(); int leafCount = 0; if (null != theNode) { for (SyncNodeElement sne : theNode.getRefs()) { if (sne.getType() == SyncNodeType.HASH) { SyncTreeEntry tste = shc.getHash(sne.getData()); if (null != tste && null != tste.getNode()) leafCount += tste.getNode().getLeafCount(); } } theNode.setLeafCount(leafCount); } return ste; } /** * Create a sync tree from the input collection of elements * @param nodeElements the elements * @param shc hash cache * @param cache node cache * @param depth starting depth (but when called nonrecursively I think it will always be 2). * @return */ public SyncTreeEntry createHeadRecursive(Collection<SyncNodeElement> nodeElements, final SyncHashCache shc, SyncNodeCache cache, int depth) { SyncTreeEntry ste = null; ArrayList<SyncNodeElement> nextElements = new ArrayList<SyncNodeElement>(); do { ste = newNodeOfNodes(nodeElements, shc, cache, depth); nextElements.add(new SyncNodeElement(ste.getHash())); } while (nodeElements.size() > 0); if (nextElements.size() > 1) return createHeadRecursive(nextElements, shc, cache, depth+1); if (Log.isLoggable(Log.FAC_SYNC, Level.FINEST) && ste.getNode().getRefs().get(0).getType() == SyncNodeType.HASH) { Log.finest(Log.FAC_SYNC, "Creating new compound node - with first element {0} and last element {1}", Component.printURI(ste.getNode().getRefs().get(0).getData()), Component.printURI(ste.getNode().getRefs().get(ste.getNode().getRefs().size() - 1).getData())); } return ste; } /** * Used to find the "first" or "last" LEAF element within the refs * @param refs * @param shc * @param start true if looking for the first element - otherwise look for last * @return */ private SyncNodeElement findit(ArrayList<SyncNodeElement> refs, SyncHashCache shc, boolean start) { int position = start ? 0 : refs.size() - 1; SyncNodeElement sne = refs.get(position); while (sne.getType() != SyncNodeType.LEAF) { SyncTreeEntry ste = shc.getHash(sne.getData()); if (null == ste || null == ste.getNode()) { return null; } if (!start) position = ste.getNode().getRefs().size() - 1; sne = ste.getNode().getElement(position); } return sne; } /** * Create a new composite node based on the input set of content names in * canonical order. * * @param names * @return entry for the new node or null if none */ public SyncTreeEntry newNode(TreeSet<ContentName> names, SyncHashCache shc, SyncNodeCache cache) { ArrayList<SyncNodeElement> snes = new ArrayList<SyncNodeElement>(); SyncTreeEntry ourEntry = null; while (names.size() > 0) { ourEntry = newLeafNode(names, shc, cache); SyncNodeElement sne = new SyncNodeElement(ourEntry.getHash()); if (names.size() > 0 || snes.size() > 0) { snes.add(sne); } } if (snes.size() > 0) { ourEntry = createHeadRecursive(snes, shc, cache, 2); } return ourEntry; } /** * Get the first or last leaf element of an arbitrary node given a cache. If we can't trace all the * way back to the leaf, return null. * @param snc the beginning node * @param cache * @param first if true looking for first * @return */ public static SyncNodeElement getFirstOrLast(SyncNodeComposite snc, SyncNodeCache cache, boolean first) { int pos = first ? 0 : snc.getRefs().size() - 1; SyncNodeElement sne = snc.getElement(pos); if (sne.getType() == SyncNodeType.LEAF) return sne; snc = cache.getNode(sne.getData()); if (null == snc) return null; return getFirstOrLast(snc, cache, first); } }