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 }