001package edu.pdx.cs410J.family; 002 003import java.io.*; 004import java.text.*; 005import java.util.*; 006 007/** 008 * This class parses the text output generated by a 009 * <code>TextDumper</code> and creates a family tree. 010 * 011 * @see TextDumper 012 */ 013public class TextParser implements Parser { 014 015 private LineNumberReader in; // Read input from here 016 private FamilyTree tree; // The family tree we're building 017 018 /////////////////////// Static Methods /////////////////////// 019 020 private static final PrintStream out = System.out; 021 022 private static void db(String s) { 023 if (Boolean.getBoolean("TextParser.DEBUG")) { 024 out.println(s); 025 } 026 } 027 028 //////////////////////// Constructors //////////////////////// 029 030 /** 031 * Creates a new text parser that reads its input from a file of a 032 * given name. 033 */ 034 public TextParser(String fileName) throws FileNotFoundException{ 035 this(new File(fileName)); 036 } 037 038 /** 039 * Creates a new text parser that reads its input from the given 040 * file. 041 */ 042 public TextParser(File file) throws FileNotFoundException { 043 this(new FileReader(file)); 044 } 045 046 /** 047 * Creates a new text parser that reads its input from the given 048 * writer. This lets us read from a sources other than files. 049 */ 050 public TextParser(Reader reader) { 051 this.in = new LineNumberReader(reader); 052 } 053 054 ////////////////////// Instance Methods ////////////////////// 055 056 /** 057 * Helper method that creates an error string and throws a 058 * <code>FamilyTreeException</code>. 059 */ 060 private void error(String message) throws FamilyTreeException { 061 int lineNumber = this.in.getLineNumber(); 062 String m = "Error at line " + lineNumber + ": " + message; 063 throw new FamilyTreeException(m); 064 } 065 066 /** 067 * Parses the specified input source and from it creates a family 068 * tree. 069 * 070 * @throws FamilyTreeException 071 * The data source is malformatted 072 */ 073 public FamilyTree parse() throws FamilyTreeException { 074 this.tree = new FamilyTree(); 075 076 // Examine each line of the file. The first line should contain a 077 // header of the form "x n" where "x" is either "P" or "M" and "n" 078 // is the number of lines the entry takes up. Parse this header 079 // and delegate the responsibility for constructing objects to 080 // other methods. 081 082 try { 083 while (this.in.ready()) { 084 String line = this.in.readLine(); 085 086 // Ignore empty lines 087 if (line == null) { 088 break; 089 090 } else if(line.equals("")) { 091 continue; 092 } 093 094 db("Read line: \"" + line + "\""); 095 096 // Parse the header line 097 String type = null; 098 String nLines = null; 099 StringTokenizer st = new StringTokenizer(line, " "); 100 if(st.hasMoreTokens()) { 101 type = st.nextToken(); 102 103 } else { 104 error("Missing type token in header"); 105 } 106 107 if(st.hasMoreTokens()) { 108 nLines = st.nextToken(); 109 110 } else { 111 error("Missing line count in header"); 112 } 113 114 try { 115 int n = Integer.parseInt(nLines); 116 if (type.equals("P")) { 117 this.parsePerson(n); 118 119 } else if (type.equals("M")) { 120 this.parseMarriage(n); 121 122 } else { 123 error("Invalid type string: " + type); 124 } 125 126 } catch (NumberFormatException ex) { 127 error("Malformatted line count: " + nLines); 128 } 129 130 } 131 132 } catch (IOException ex) { 133 int lineNumber = this.in.getLineNumber(); 134 String m = "Parsing error at line " + lineNumber; 135 throw new FamilyTreeException(m); 136 } 137 138 // Okay, we're all done parsing the tree now we need to "patch up" 139 // the Person objects to make sure that their mothers and fathers 140 // all exist. 141 Iterator people = this.tree.getPeople().iterator(); 142 while (people.hasNext()) { 143 Person person = (Person) people.next(); 144 person.patchUp(this.tree); 145 } 146 147 return this.tree; 148 } 149 150 /** 151 * Helper method that parses the source, creates a 152 * <code>Person</code>, and adds it to the family tree. 153 */ 154 private void parsePerson(int nLines) throws FamilyTreeException { 155 Person person = null; 156 157 DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); 158 159 for (int i = 0; i < nLines; i++) { 160 String line = null; 161 try { 162 if(!this.in.ready()) { 163 // The reader should be ready, Houston we have a problem! 164 error("Unexpected end of file"); 165 } 166 167 line = this.in.readLine(); 168 169 } catch (IOException ex) { 170 error("IOException: " + ex.getMessage()); 171 } 172 173 if (line == null) { 174 break; 175 } 176 177 // Ignore empty lines 178 if (line.equals("")) { 179 continue; 180 } 181 182 db("Read line: \"" + line + "\""); 183 184 // Parse the line 185 String key = null; 186 String value = null; 187 188 StringTokenizer st = new StringTokenizer(line, ":"); 189 190 if (st.hasMoreTokens()) { 191 key = st.nextToken(); 192 193 } else { 194 error("No key specified"); 195 } 196 197 if (st.hasMoreTokens()) { 198 StringBuffer sb = new StringBuffer(); 199 while(st.hasMoreTokens()) { 200 sb.append(st.nextToken() + " "); 201 } 202 value = sb.toString().trim(); 203 204 } else { 205 error("No value specified"); 206 } 207 208 // Now do a "switch" on the key and parse the value 209 // appropriately 210 if (key.equals("id")) { 211 // id 212 try { 213 int id = Integer.parseInt(value); 214 if (this.tree.getPerson(id) != null) { 215 error("FamilyTree already has person " + id); 216 217 } else { 218 // Call this Person constructor because we will fill in 219 // the gender later 220 person = new Person(id); 221 this.tree.addPerson(person); 222 } 223 224 } catch (NumberFormatException ex) { 225 error("Malformatted id: " + value); 226 } 227 228 } else if (key.equals("g")) { 229 // gender 230 if (person != null) { 231 try { 232 Person.Gender gender = Person.Gender.valueOf(value); 233 person.setGender(gender); 234 db("Set gender of " + person + " to " + gender); 235 236 } catch (IllegalArgumentException ex) { 237 error("Malformed gender: " + value); 238 } 239 240 } else { 241 error("Id must be specified before gender"); 242 } 243 244 } else if (key.equals("fn")) { 245 // firstName 246 if(person != null) { 247 person.setFirstName(value); 248 249 } else { 250 error("Id must be specified before first name"); 251 } 252 253 } else if (key.equals("mn")) { 254 // middleName 255 if(person != null) { 256 person.setMiddleName(value); 257 258 } else { 259 error("Id must be specified before middle name"); 260 } 261 262 } else if (key.equals("ln")) { 263 // lastName 264 if(person != null) { 265 person.setLastName(value); 266 267 } else { 268 error("Id must be specified before last name"); 269 } 270 271 } else if (key.equals("f")) { 272 // father 273 if(person != null) { 274 try { 275 int fatherId = Integer.parseInt(value); 276 person.setFatherId(fatherId); 277 278 } catch (NumberFormatException ex) { 279 error("Malformatted father id: " + value); 280 } 281 282 } else { 283 error("Id must be specified before father"); 284 } 285 286 } else if (key.equals("m")) { 287 // mother 288 if(person != null) { 289 try { 290 int motherId = Integer.parseInt(value); 291 person.setMotherId(motherId); 292 293 } catch (NumberFormatException ex) { 294 error("Malformatted mother id: " + value); 295 } 296 297 } else { 298 error("Id must be specified before mother"); 299 } 300 301 } else if (key.equals("dob")) { 302 // date of birth 303 if(person != null) { 304 try { 305 person.setDateOfBirth(df.parse(value)); 306 307 } catch (ParseException ex) { 308 error("Malformatted date of birth: " + value); 309 } 310 311 } else { 312 error("Id must be specified before date of birth"); 313 } 314 315 } else if (key.equals("dod")) { 316 // date of death 317 if(person != null) { 318 try { 319 person.setDateOfDeath(df.parse(value)); 320 321 } catch (ParseException ex) { 322 error("Malformatted date of death: " + value); 323 } 324 325 } else { 326 error("Id must be specified before date of death"); 327 } 328 329 } else { 330 error("Unknown person key: " + key); 331 } 332 } 333 } 334 335 /** 336 * Helper method that parses the source and create marriages. 337 */ 338 private void parseMarriage(int nLines) throws FamilyTreeException { 339 Marriage marriage = null; 340 341 DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); 342 343 if (nLines < 1) { 344 error("Missing ids in marriage"); 345 } 346 347 String line = null; 348 try { 349 if (!this.in.ready()) { 350 // The reader should be ready, Houston we have a problem! 351 error("Unexpected end of file"); 352 } 353 354 line = this.in.readLine(); 355 356 } catch (IOException ex) { 357 error("IOException: " + ex.getMessage()); 358 } 359 360 // The first line should be the id of the husband followed by the 361 // id of the wife 362 Person husband = null; 363 Person wife = null; 364 StringTokenizer st = new StringTokenizer(line, " "); 365 if (st.hasMoreTokens()) { 366 String s = st.nextToken(); 367 try { 368 int husbandId = Integer.parseInt(s); 369 husband = this.tree.getPerson(husbandId); 370 371 } catch (NumberFormatException ex) { 372 error("Malformatted husband id: " + s); 373 } 374 375 } else { 376 error("Missing husband id"); 377 } 378 379 if (st.hasMoreTokens()) { 380 String s = st.nextToken(); 381 try { 382 int wifeId = Integer.parseInt(s); 383 wife = this.tree.getPerson(wifeId); 384 385 } catch (NumberFormatException ex) { 386 error("Malformatted wife id: " + s); 387 } 388 389 } else { 390 error("Missing wife id"); 391 } 392 393 marriage = new Marriage(husband, wife); 394 wife.addMarriage(marriage); 395 husband.addMarriage(marriage); 396 397 // Parse the rest 398 for (int i = 1; i < nLines; i++) { 399 line = null; 400 try { 401 if(!this.in.ready()) { 402 // The reader should be ready, Houston we have a problem! 403 error("Unexpected end of file"); 404 } 405 406 line = this.in.readLine(); 407 408 } catch (IOException ex) { 409 error("IOException: " + ex.getMessage()); 410 } 411 412 // Ignore empty lines 413 if (line.equals("")) { 414 continue; 415 } 416 417 418 // Parse the line 419 String key = null; 420 String value = null; 421 422 st = new StringTokenizer(line, ":"); 423 424 if (st.hasMoreTokens()) { 425 key = st.nextToken(); 426 427 } else { 428 error("No key specified"); 429 } 430 431 if (st.hasMoreTokens()) { 432 StringBuffer sb = new StringBuffer(); 433 while (st.hasMoreTokens()) { 434 sb.append(st.nextToken() + " "); 435 } 436 value = sb.toString().trim(); 437 438 } else { 439 error("No value specified for key " + key); 440 } 441 442 // Now do a "switch" on the key and parse the value 443 // appropriately 444 if (key.equals("d")) { 445 // date 446 try { 447 marriage.setDate(df.parse(value)); 448 449 } catch (ParseException ex) { 450 error("Malformatted marriage date: " + value); 451 } 452 453 } else if (key.equals("l")) { 454 marriage.setLocation(value); 455 456 } else { 457 error("Unknown marriage key: " + key); 458 } 459 } 460 } 461 462 /** 463 * Test program. Parse the file that is given on the command line. 464 * Pretty print the resulting family tree. 465 */ 466 public static void main(String[] args) { 467 if (args.length == 0) { 468 System.err.println("** Missing file name"); 469 System.exit(1); 470 } 471 472 // Parse the input file 473 String fileName = args[0]; 474 try { 475 TextParser parser = new TextParser(fileName); 476 FamilyTree tree = parser.parse(); 477 478 PrintWriter out = new PrintWriter(System.out, true); 479 PrettyPrinter pretty = new PrettyPrinter(out); 480 pretty.dump(tree); 481 482 } catch (FileNotFoundException ex) { 483 System.err.println("** Could not find file " + fileName); 484 485 } catch (FamilyTreeException ex) { 486 System.err.println("** " + ex.getMessage()); 487 } 488 } 489 490}