### Question:
AfpFilterChain { public void filter(ChainLink... chainLinks) throws IOException { boolean need2Pass = false; for(ChainLink link : chainLinks) { if(link.needTwoPass()) { need2Pass = true; break; } } if(!need2Pass) { doFilter(in, out, chainLinks); return; } LinkedList<ChainLink> links = new LinkedList<ChainLink>(Arrays.asList(chainLinks)); File currentIn = in; File currentOut = null; ChainLink twoPassLink = null; do { LinkedList<ChainLink> toFilter = new LinkedList<ChainLink>(); for(ChainLink link : links) { if(link.needTwoPass() && link != twoPassLink) { twoPassLink = link; break; } toFilter.add(link); } if(!toFilter.isEmpty()) { currentOut = File.createTempFile("filter", ".afp"); currentOut.delete(); currentOut.deleteOnExit(); doFilter(currentIn, currentOut, (ChainLink[]) toFilter.toArray(new ChainLink[toFilter.size()])); if(currentIn != in) { currentIn.delete(); } currentIn = currentOut; } else { doRead(currentIn, twoPassLink); } links.removeAll(toFilter); } while(!links.isEmpty()); copy(currentOut, out); } AfpFilterChain(File in, File out); void filter(ChainLink... chainLinks);  }### Answer:
@Test public void testNoop() throws IOException { File in = new File("testdata/asciiComment.afp"); File out = File.createTempFile("test", ".afp"); out.delete(); out.deleteOnExit(); AfpFilterChain chain = new AfpFilterChain(in, out); chain.filter(new ChainLink() { @Override public boolean onStructuredField(Chain chain, SF sf) throws IOException { chain.commit(this, sf); return true; } @Override public boolean needTwoPass() { return false; } }); AfpInputStream a1 = new AfpInputStream(new FileInputStream(in)); AfpInputStream a2 = new AfpInputStream(new FileInputStream(out)); try { SF sf1, sf2; while((sf1 = a1.readStructuredField()) != null) { sf2 = a2.readStructuredField(); assertTrue(EcoreUtil.equals(sf1, sf2)); } } finally { a1.close(); a2.close(); } }
@Test public void testNoOutput() throws IOException { File in = new File("testdata/asciiComment.afp"); AfpFilterChain chain = new AfpFilterChain(in, null); chain.filter(new ChainLink() { @Override public boolean onStructuredField(Chain chain, SF sf) throws IOException { chain.commit(this, sf); return true; } @Override public boolean needTwoPass() { return false; } }); }
@Test public void testFilterStop() throws IOException { File in = new File("testdata/asciiComment.afp"); AfpFilterChain chain = new AfpFilterChain(in, null); chain.filter(new ChainLink() { private boolean once = false; @Override public boolean onStructuredField(Chain chain, SF sf) throws IOException { if(once) throw new IllegalAccessError(); chain.commit(this, sf); once = true; return false; } @Override public boolean needTwoPass() { return false; } }); }
@Test public void testTwoFilterStop() throws IOException { File in = new File("testdata/asciiComment.afp"); AfpFilterChain chain = new AfpFilterChain(in, null); chain.filter(new ChainLink() { private boolean once = false; @Override public boolean onStructuredField(Chain chain, SF sf) throws IOException { if(once) throw new IllegalAccessError(); chain.commit(this, sf); once = true; return false; } @Override public boolean needTwoPass() { return false; } },new ChainLink() { private boolean once = false; @Override public boolean onStructuredField(Chain chain, SF sf) throws IOException { if(once) throw new IllegalAccessError(); chain.commit(this, sf); once = true; return false; } @Override public boolean needTwoPass() { return false; } }); }