public class PairFilter {
    private final java.util.Map<Character, java.util.Set<Character>> pairMap; //close -> open
    private final java.util.Set<Character> allOpenChars;
    private final java.util.List<Character> unclosedOpenChars; //really a stack, but then the iterator would go backwards
    
    private PairFilter(java.util.Map<Character, java.util.Set<Character>> pairMap, java.util.Set<Character> allOpenChars) {
        this.pairMap = pairMap;
        this.allOpenChars = allOpenChars;
        this.unclosedOpenChars = new java.util.ArrayList<Character>();
    }
    
    public static class Builder {
        private final java.util.Map<Character, java.util.Set<Character>> pairMap; //close -> open
        private final java.util.Set<Character> allOpenChars;

        public Builder() {
            this.pairMap = new java.util.HashMap<Character, java.util.Set<Character>>();
            this.allOpenChars = new java.util.HashSet<Character>();
        }
        
        public Builder add(char open, char close) {
            allOpenChars.add(open);
            java.util.Set<Character> list = pairMap.get(close);
            if(list == null) {
                list = new java.util.HashSet<Character>();
                pairMap.put(close, list);
            }
            list.add(open);
            return this;
        }
        
        public PairFilter build() {
            return new PairFilter(pairMap, allOpenChars);
        }
    }
    
    public boolean test(char ch) {
        //NB: closing-ness takes precedence over opening-ness
        java.util.Set<Character> correspondingOpens = pairMap.get(ch);
        if(correspondingOpens != null) { //implies closing
            for(java.util.Iterator<Character> it = unclosedOpenChars.iterator(); it.hasNext(); ) {
                char open = it.next();
                if(correspondingOpens.contains(open)) {
                    //if closing and have seen corresponding open, then close open and reject
                    it.remove();
                    return false;
                }
            }
            //if closing and have not seen corresponding open, then accept
        }
        if(allOpenChars.contains(ch)) { //implies opening
            unclosedOpenChars.add(0, ch); //insert at beginning
        }
        return true;
    }
    
    public static void main(String[] args) {
        PairFilter filter;
        System.err.println("Test");
        filter = new PairFilter.Builder().build();
        for(char ch : "hello".toCharArray()) {
            System.out.println(ch + " - " + filter.test(ch));
        }
        System.err.println("Test");
        filter = new PairFilter.Builder().add('(', ')').build();
        for(char ch : "(hello)".toCharArray()) {
            System.out.println(ch + " - " + filter.test(ch));
        }
        System.err.println("Test");
        filter = new PairFilter.Builder().add('(', ')').build();
        for(char ch : "(hello))".toCharArray()) {
            System.out.println(ch + " - " + filter.test(ch));
        }
        System.err.println("Test");
        filter = new PairFilter.Builder().add('l', 'l').build();
        for(char ch : "(hello))".toCharArray()) {
            System.out.println(ch + " - " + filter.test(ch));
        }
    }
}