class FilePrintWriter extends PrintWriter {
        File file; 
        public FilePrintWriter(File file) throws FileNotFoundException {
            super(new BufferedOutputStream(new FileOutputStream(file)));
            this.file = file; 
        }
        public File getFile() {
            return file;
        }
    }
    

    /**
     * Whether to apply the mapping to a URI being processed itself, for example
     * early in processing (while its status is still 'unattempted').
     */
    protected boolean checkUri = true;
    public boolean getCheckUri() {
        return this.checkUri;
    }
    public void setCheckUri(boolean check) {
        this.checkUri = check;
    }

    /**
     * Whether to apply the mapping to discovered outlinks, for example after
     * extraction has occurred.
     */
    protected boolean checkOutlinks = true;
    public boolean getCheckOutlinks() {
        return this.checkOutlinks;
    }
    public void setCheckOutlinks(boolean check) {
        this.checkOutlinks = check;
    }

    /** 
     * Decide rules to determine if an outlink is subject to mapping.
     */ 
    protected DecideRule outlinkRule = new AcceptDecideRule(); 
    public DecideRule getOutlinkRule() {
        return this.outlinkRule;
    }
    public void setOutlinkRule(DecideRule rule) {
        this.outlinkRule = rule; 
    }

    /**
     * Name of local crawler node; mappings to this name result in normal
     * processing (no diversion).
     */
    protected String localName = ".";
    public String getLocalName() {
        return this.localName;
    }
    public void setLocalName(String name) {
        this.localName = name; 
    }
    
    /**
     * Directory to write diversion logs.
     */
    protected ConfigPath diversionDir = 
        new ConfigPath("diverted URIs subdirectory","diversions");
    public ConfigPath getDiversionDir() {
        return this.diversionDir;
    }
    public void setDiversionDir(ConfigPath path) {
        this.diversionDir = path; 
    }
    
    /**
     * Number of timestamp digits to use as prefix of log names (grouping all
     * diversions from that period in a single log). Default is 10 (hourly log
     * rotation).
     * 
     */
    protected int rotationDigits = 10; 
    public int getRotationDigits() {
        return this.rotationDigits;
    }
    public void setRotationDigits(int digits) {
        this.rotationDigits = digits; 
    }

    /**
     * Mapping of target crawlers to logs (PrintWriters)
     */
    protected HashMap<String,PrintWriter> diversionLogs
     = new HashMap<String,PrintWriter>();

    /**
     * Truncated timestamp prefix for diversion logs; when
     * current time doesn't match, it's time to close all
     * current logs. 
     */
    protected String logGeneration = "";
        
    protected ArrayLongFPCache cache;
   
    /**
     * Constructor.
     */
    public CrawlMapper() {
        super();
    }

    
    @Override
    protected boolean shouldProcess(CrawlURI puri) {
        return true;
    }
    
    @Override
    protected void innerProcess(CrawlURI puri) {
        throw new AssertionError();
    }

    @Override
    protected ProcessResult innerProcessResult(CrawlURI puri) {<FILL_FUNCTION_BODY>

        CrawlURI curi = (CrawlURI)puri;
        String nowGeneration = 
            ArchiveUtils.get14DigitDate().substring(
                        0,
                        getRotationDigits());
        if(!nowGeneration.equals(logGeneration)) {
            updateGeneration(nowGeneration);
        }
        
        if (curi.getFetchStatus() <= 0  // unfetched/unsuccessful
                && getCheckUri()) {
            // apply mapping to the CrawlURI itself
            String target = map(curi);
            if(!localName.equals(target)) {
                // CrawlURI is mapped to somewhere other than here
                curi.setFetchStatus(S_BLOCKED_BY_CUSTOM_PROCESSOR);
                curi.getAnnotations().add("to:"+target);
                divertLog(curi,target);
                return ProcessResult.FINISH;
            } else {
                // localName means keep locally; do nothing
            }
        }
        
        if (getCheckOutlinks()) {
            // consider outlinks for mapping
            Iterator<CrawlURI> iter = curi.getOutLinks().iterator(); 
            while(iter.hasNext()) {
                CrawlURI cauri = iter.next();
                if (decideToMapOutlink(cauri)) {
                    // apply mapping to the CrawlURI
                    String target = map(cauri);
                    if(!localName.equals(target)) {
                        // CrawlURI is mapped to somewhere other than here
                        iter.remove();
                        divertLog(cauri,target);
                    } else {
                        // localName means keep locally; do nothing
                    }
                }
            }
        }
        return ProcessResult.PROCEED;
    
<methods>public non-sealed void <init>() ,public void doCheckpoint(org.archive.checkpointing.Checkpoint) throws java.io.IOException,public void finishCheckpoint(org.archive.checkpointing.Checkpoint) ,public static java.lang.String flattenVia(org.archive.modules.CrawlURI) ,public java.lang.String getBeanName() ,public boolean getEnabled() ,public org.archive.spring.KeyedProperties getKeyedProperties() ,public static long getRecordedSize(org.archive.modules.CrawlURI) ,public org.archive.modules.deciderules.DecideRule getShouldProcessRule() ,public long getURICount() ,public static boolean hasHttpAuthenticationCredential(org.archive.modules.CrawlURI) ,public boolean isRunning() ,public static boolean isSuccess(org.archive.modules.CrawlURI) ,public org.archive.modules.ProcessResult process(org.archive.modules.CrawlURI) throws java.lang.InterruptedException,public java.lang.String report() ,public void setBeanName(java.lang.String) ,public void setEnabled(boolean) ,public void setRecoveryCheckpoint(org.archive.checkpointing.Checkpoint) ,public void setShouldProcessRule(org.archive.modules.deciderules.DecideRule) ,public void start() ,public void startCheckpoint(org.archive.checkpointing.Checkpoint) ,public void stop() <variables>protected java.lang.String beanName,protected boolean isRunning,protected org.archive.spring.KeyedProperties kp,protected org.archive.checkpointing.Checkpoint recoveryCheckpoint,protected java.util.concurrent.atomic.AtomicLong uriCount