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}