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