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 accept(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 accept(StreetSegment seg) {
102 return filter1.accept(seg) && filter2.accept(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 accept(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 /*@Nullable*/ String line;
134 private /*@Nullable*/ 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 }