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 }