class URIAuthorityBasedQueueAssignmentPolicy
extends 
 QueueAssignmentPolicy 
implements
 HasKeyedProperties {
    private static final long serialVersionUID = 3L;
    
    //for when neat class-key fails us
    protected static String DEFAULT_CLASS_KEY = "default...";

    protected LongToIntConsistentHash conhash = new LongToIntConsistentHash();
    
    /**
     * Whether to always defer to a previously-assigned key inside 
     * the CrawlURI. If true, any key already in the CrawlURI will
     * be returned as the classKey. 
     */
    public boolean getDeferToPrevious() {
        return (Boolean) kp.get("deferToPrevious");
    }
    {
        setDeferToPrevious(true);
    }
    public void setDeferToPrevious(boolean defer) {
        kp.put("deferToPrevious",defer);
    }
    
    /**
     * The number of parallel queues to split a core key into. By 
     * default is 1. If larger than 1, the non-authority-based portion
     * of the URI will be used to distribute over that many separate
     * queues. 
     * 
     */
    public int getParallelQueues() {
        return (Integer) kp.get("parallelQueues");
    }
    {
        setParallelQueues(1);
    }
    public void setParallelQueues(int count) {
        kp.put("parallelQueues",count);
    }

    /**
    * Whether to assign URIs to parallel queues in round-robin fashon.
    * False by default. If true, URIs will be assigned to a queue randomly.
    */
    public boolean getParallelQueuesRandomAssignment() {
        return (Boolean) kp.get("parallelQueuesRandomAssignment");
    }
    {
        setParallelQueuesRandomAssignment(false);
    }
    public void setParallelQueuesRandomAssignment(boolean doRandom) {
        kp.put("parallelQueuesRandomAssignment",doRandom);
    }

    public String getClassKey(CrawlURI curi) {
        if(getDeferToPrevious() && !StringUtils.isEmpty(curi.getClassKey())) {
            return curi.getClassKey();
        }

        UURI basis = curi.getPolicyBasisUURI();
        String candidate = getCoreKey(basis);
        
        if(!StringUtils.isEmpty(getForceQueueAssignment())) {
            candidate = getForceQueueAssignment();
        }
        
        // all whois urls in the same queue
        if (curi.getUURI().getScheme().equals("whois")) {
            return "whois...";
        }
        
        if(StringUtils.isEmpty(candidate)) {
            return DEFAULT_CLASS_KEY;
        }
        
        if(getParallelQueues()>1) {
            int subqueue = getSubqueue(basis,getParallelQueues());
            if (subqueue>0) {
                candidate += "+"+subqueue;
            }
        }
        return candidate; 
    }
    
    protected int getSubqueue(UURI basisUuri, int parallelQueues) {<FILL_FUNCTION_BODY>}
    
    /**
     * Base subqueue on first path-segment, if any. (Means unbalanced
     * subqueues, but consistency for most-common case where fanout
     * can be at first segment, and it's beneficial to keep similar
     * URIs in same queue.)
     * @param uuri
     * @return
     */
    protected String bucketBasis(UURI uuri) {
        if(getParallelQueuesRandomAssignment()){
            return uuri.getEscapedURI();
        }
        String path = new String(uuri.getRawPath());
        int i = path.indexOf('/',1);
        if(i<0) {
            return null; 
        }
        return path.substring(1,i);
    }

    protected abstract String getCoreKey(UURI basis);
}

        String basis = bucketBasis(basisUuri);
        if(StringUtils.isEmpty(basis)) {
            return 0; 
        }
        return conhash.bucketFor(basis, parallelQueues);
    
<methods>public non-sealed void <init>() ,public abstract java.lang.String getClassKey(org.archive.modules.CrawlURI) ,public java.lang.String getForceQueueAssignment() ,public org.archive.spring.KeyedProperties getKeyedProperties() ,public int maximumNumberOfKeys() ,public void setForceQueueAssignment(java.lang.String) <variables>protected org.archive.spring.KeyedProperties kp,private static final long serialVersionUID