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 }