001    package ps6;
002    
003    import ps4.*;
004    import java.io.*;
005    import java.util.*;
006    
007    import ps2.GeoPoint;
008    
009    /**
010     * Abstraction to read and return filters based on kill files
011     **/
012    public class KillfileReader
013    {
014        private static boolean debug = false;
015        private static void debugln(String s) { if (debug) { System.err.println("KFR: " + s); } }
016    
017        /**
018         * @return a filter which passes segments not listed in the given kill file
019         **/
020        public static StreetSegmentFilter fromFile(File killfile){
021            debugln("Reading " + killfile);
022            return new KillfileFilter(killfile);
023        }
024    
025        /**
026         * @return a filter which passes segments not listed in $(dbdir)/*killfile.txt
027         **/
028        public static StreetSegmentFilter fromDir(File dbdir){
029            if (!dbdir.isDirectory()) {
030                throw new KillfileException("Not a directory: " + dbdir);
031            }
032    
033            File[] files = dbdir.listFiles(killfile_filter);
034            if (files.length == 0) {
035                return new AllPassStreetSegmentFilter();
036            }
037    
038            StreetSegmentFilter result = new KillfileFilter(files[0]);
039            for (File f : files) {
040                StreetSegmentFilter another = new KillfileFilter(f);
041                result = new CompositeStreetSegmentFilter(result, another);
042            }
043    
044            return result;
045        }
046    
047        /** filter which gives us *killfile.txt files */
048        private static final FilenameFilter killfile_filter = new FNFilter();
049        private static class FNFilter
050            implements FilenameFilter{
051            /**
052             * @param d an arbitrary File
053             * @param name the string to apply the filter to
054             * @return true iff name ends with "killfile.txt" 
055             */
056            public boolean accept(File d, String name) {
057                return (name.toLowerCase().endsWith("killfile.txt"));
058            }
059        }
060    
061        /**
062         * Indicates that a killfile was non-existant, malformed, etc.
063         **/
064        public static class KillfileException
065            extends RuntimeException{
066            public static final long serialVersionUID = 4534;
067            public KillfileException(String s) { super(s); }
068        }
069    
070        /**
071         * Filter which allows all segments through
072         **/
073        private static class AllPassStreetSegmentFilter
074            implements StreetSegmentFilter
075        {
076            public boolean apply(StreetSegment seg)
077            {
078                return true;
079            }
080        }
081    
082        /**
083         * Filter which composes two other filters
084         **/
085        private static class CompositeStreetSegmentFilter
086            implements StreetSegmentFilter{
087      
088            public CompositeStreetSegmentFilter(StreetSegmentFilter one, StreetSegmentFilter two){
089                this.one = one;
090                this.two = two;
091            }
092    
093            private final StreetSegmentFilter one;
094            private final StreetSegmentFilter two;
095      
096            public boolean apply(StreetSegment seg){
097                return one.apply(seg) && two.apply(seg);
098            }
099        }
100    
101        /**
102         * Filter which filters based on some killfile
103         **/
104        private static class KillfileFilter
105            implements StreetSegmentFilter{
106    
107            public boolean apply(StreetSegment seg)
108            {
109                if (kill.contains(seg)) {
110                    KillfileReader.debugln("Killed " + seg);
111                    return false;
112                }
113    
114                if (limit_one.contains(seg)) {
115                    KillfileReader.debugln("First hit on " + seg);
116                    limit_one.remove(seg);
117                    kill.add(seg);
118                }
119    
120                return true;
121            }
122    
123            private final Set<StreetSegment> kill = new HashSet<StreetSegment>();
124            private final Set<StreetSegment> limit_one = new HashSet<StreetSegment>();
125    
126    
127            // not a real field, but want scope in c-tor and helper
128            private String line;
129            private String orig_line;
130    
131            private KillfileFilter(File killfile){
132                try {
133          
134                    BufferedReader lines = new BufferedReader(new InputStreamReader(new FileInputStream(killfile)));
135                    while ((orig_line = line = lines.readLine()) != null) {
136                        // e.g. "KILL$(unnamed street)$41271599$-70181563$41273016$-70172648$1$$$     $     $UNKNOWN"
137            
138                        String command = nextToken();
139                        String name = nextToken();
140                        int p1_lat = parseInt(nextToken());
141                        int p1_long = parseInt(nextToken());
142                        int p2_lat = parseInt(nextToken());
143                        int p2_long = parseInt(nextToken());
144                        boolean inc = (parseInt(nextToken()) != 0);
145                        StreetNumberSet leftNum = makeSNS(nextToken());
146                        StreetNumberSet rightNum = makeSNS(nextToken());
147                        String leftZip = nextToken();
148                        String rightZip = nextToken();
149                        StreetClassification streetClass = StreetClassification.parse(nextToken());
150    
151                        StreetSegment seg =
152                            new StreetSegment(new GeoPoint(p1_lat, p1_long),
153                                              new GeoPoint(p2_lat, p2_long),
154                                              name,
155                                              leftNum,
156                                              rightNum,
157                                              leftZip,
158                                              rightZip,
159                                              streetClass,
160                                              inc);
161    
162                        if ("WARNING".equals(command)) {
163                            // for now, just ignore these
164                        } else if ("KILL".equals(command)) {
165                            kill.add(seg);
166                        } else if ("LIMIT_ONE".equals(command)) {
167                            limit_one.add(seg);
168                        } else {
169                            throw new KillfileReader.KillfileException("Unknown command: " + command);
170                        }   
171                    }
172    
173                } catch (IOException e) {
174                    throw new KillfileReader.KillfileException(e.getClass() + e.getMessage());
175                }
176            }
177    
178            /**
179             * Wraps Integer.parseInt with class-specific exception handling
180             * @param num number to parse to an integer
181             * @return a string representation of the number
182             */
183            private int parseInt(String num) {
184                try {
185                    return Integer.parseInt(num);
186                } catch (NumberFormatException e) {
187                    throw new KillfileReader.KillfileException(e.getClass() + e.getMessage() + "'" + orig_line + "'");
188                }
189            }
190    
191            /**
192             * Convenience function for creating street number sets
193             * @param sns string representation of street number set
194             * @return street number set corresponding to sns
195             */
196            private StreetNumberSet makeSNS(String sns){
197                return new StreetNumberSet(sns);
198            }
199        
200            private String nextToken(){
201                if (line == null) {
202                    throw new KillfileException("Ran out of tokens");
203                }
204    
205                String result;
206                int n = line.indexOf('$');
207                if (n >= 0) {
208                    result = line.substring(0, n);
209                    line = line.substring(n+1);
210                } else {
211                    result = line;
212                    line = null;
213                }
214    
215                return result;
216            }
217    
218        }
219    
220    }