class PendingItem implements Comparable<PendingItem> {
        long fp;
        CrawlURI caUri;
        public PendingItem(long fp, CrawlURI value) {
            this.fp = fp;
            this.caUri = value;
        }
        public int compareTo(PendingItem vs) {
            return (fp < vs.fp) ? -1 : ( (fp == vs.fp) ? 0 : 1); 
        }
    }
    
    private static Logger LOGGER =
        Logger.getLogger(FPMergeUriUniqFilter.class.getName());

    protected CrawlUriReceiver receiver;
    protected PrintWriter profileLog;
    
    // statistics
    protected long quickDuplicateCount = 0;
    protected long quickDupAtLast = 0; 
    protected long pendDuplicateCount = 0;
    protected long pendDupAtLast = 0; 
    protected long mergeDuplicateCount = 0;
    protected long mergeDupAtLast = 0; 
    
    /** items awaiting merge
     * TODO: consider only sorting just pre-merge
     * TODO: consider using a fastutil long-&gt;Object class
     * TODO: consider actually writing items to disk file,
     * as in Najork/Heydon
     */
    protected TreeSet<PendingItem> pendingSet = new TreeSet<PendingItem>();
    
    /** size at which to force flush of pending items */
    protected int maxPending = DEFAULT_MAX_PENDING;
    public static final int DEFAULT_MAX_PENDING = 10000; 
    // TODO: increase
    
    /**
     * time-based throttle on flush-merge operations
     */
    protected long nextFlushAllowableAfter = 0;
    public static final long FLUSH_DELAY_FACTOR = 100;

    /** cache of most recently seen FPs */
    protected ArrayLongFPCache quickCache = new ArrayLongFPCache();
    // TODO: make cache most-often seen, not just most-recent
    
    protected AtomicLong addedCount = new AtomicLong();

    public FPMergeUriUniqFilter() {
        super();
        String profileLogFile = 
            System.getProperty(FPMergeUriUniqFilter.class.getName()
                + ".profileLogFile");
        if (profileLogFile != null) {
            setProfileLog(new File(profileLogFile));
        }
    }

    public void setMaxPending(int max) {
        maxPending = max;
    }
    
    public long pending() {
        return pendingSet.size();
    }

    @Override
    public long addedCount() {
        return addedCount.get();
    }

    public void setDestination(CrawlUriReceiver receiver) {
        this.receiver = receiver;
    }

    protected void profileLog(String key) {
        if (profileLog != null) {
            profileLog.println(key);
        }
    }
    
    /* (non-Javadoc)
     * @see org.archive.crawler.datamodel.UriUniqFilter#add(java.lang.String, org.archive.crawler.datamodel.CrawlURI)
     */
    public synchronized void add(String key, CrawlURI value) {
        addedCount.incrementAndGet();
        profileLog(key);
        long fp = createFp(key); 
        if(! quickCheck(fp)) {
            quickDuplicateCount++;
            return; 
        }
        pend(fp,value);
        if (pendingSet.size()>=maxPending) {
            flush();
        }
    }

    /**
     * Place the given FP/CrawlURI pair into the pending set, awaiting
     * a merge to determine if it's actually accepted. 
     * 
     * @param fp long fingerprint
     * @param value CrawlURI or null, if fp only needs merging (as when 
     * CrawlURI was already forced in
     */
    protected void pend(long fp, CrawlURI value) {<FILL_FUNCTION_BODY>

        // special case for first batch of adds
        if(count()==0) {
            if(pendingSet.add(new PendingItem(fp,null))==false) {
                pendDuplicateCount++; // was already present
            } else {
                // since there's no prior list to merge, push uri along right now
                if(value!=null) {
                    this.receiver.receive(value);
                }
            }
            return;
        }
        if(pendingSet.add(new PendingItem(fp,value))==false) {
            pendDuplicateCount++; // was already present
        }
    
<no_super_class>