001package edu.pdx.cs410J.family;
002
003import java.io.File;
004import java.io.FileWriter;
005import java.io.IOException;
006import java.io.PrintWriter;
007import java.text.DateFormat;
008import java.text.ParseException;
009import java.util.Calendar;
010import java.util.Date;
011import java.util.HashSet;
012import java.util.Iterator;
013import java.util.Set;
014
015import javax.xml.parsers.DocumentBuilder;
016import javax.xml.parsers.DocumentBuilderFactory;
017import javax.xml.parsers.ParserConfigurationException;
018import javax.xml.transform.OutputKeys;
019import javax.xml.transform.Result;
020import javax.xml.transform.Source;
021import javax.xml.transform.Transformer;
022import javax.xml.transform.TransformerException;
023import javax.xml.transform.TransformerFactory;
024import javax.xml.transform.dom.DOMSource;
025import javax.xml.transform.stream.StreamResult;
026
027import org.w3c.dom.DOMException;
028import org.w3c.dom.DOMImplementation;
029import org.w3c.dom.Document;
030import org.w3c.dom.DocumentType;
031import org.w3c.dom.Element;
032
033/**
034 * This class dumps a family tree to a destination (for example, a
035 * file) in XML format.  This file is meant to be used by an
036 * <code>XmlParser</code> to create a <code>FamilyTree</code>.
037 */
038public class XmlDumper extends XmlHelper implements Dumper {
039  private static PrintWriter err = new PrintWriter(System.err, true);
040
041  private PrintWriter pw;      // Dumping destination
042
043  /**
044   * Creates a new XML dumper that dumps to a file of a given name.
045   * If the file does not exist, it is created.
046   */
047  public XmlDumper(String fileName) throws IOException {
048    this(new File(fileName));
049  }
050
051  /**
052   * Creates a new XML dumper that dumps to a given file.
053   */
054  public XmlDumper(File file) throws IOException {
055    this(new PrintWriter(new FileWriter(file), true));
056  }
057
058  /**
059   * Creates a new XML dumper that prints to a
060   * <code>PrintWriter</code>.  This way, we can dump to destinations
061   * other than files.
062   */
063  public XmlDumper(PrintWriter pw) {
064    this.pw = pw;
065  }
066
067  /**
068   * Given a <code>Date</code> returns a chunk of a DOM tree
069   * representing that date.
070   */
071  private static Element getDateElement(Document doc, Date date) {
072    // Have to do all sorts of yucky Calendar stuff
073    Calendar cal = Calendar.getInstance();
074    cal.setTime(date);
075
076    // Make a date Element
077    Element d = doc.createElement("date");
078    
079    int month = cal.get(Calendar.MONTH);
080    Element m = doc.createElement("month");
081    d.appendChild(m);
082    m.appendChild(doc.createTextNode(String.valueOf(month)));
083
084    int day = cal.get(Calendar.DATE);
085    Element dy = doc.createElement("day");
086    d.appendChild(dy);
087    dy.appendChild(doc.createTextNode(String.valueOf(day)));
088
089    int year = cal.get(Calendar.YEAR);
090    Element y = doc.createElement("year");
091    d.appendChild(y);
092    y.appendChild(doc.createTextNode(String.valueOf(year)));
093
094    // Return the date Element
095    return d;
096  }
097
098  /**
099   * Dumps the contents of a family tree to the desired destination.
100   *
101   * @throws FamilyTreeException
102   *         An error occurred while dumping the family tree
103   */
104  public void dump(FamilyTree tree) {
105    // First we create a DOM tree that represents the family tree
106    Document doc = null;
107
108    try {
109      DocumentBuilderFactory factory =
110        DocumentBuilderFactory.newInstance();
111      factory.setValidating(true);
112
113      DocumentBuilder builder = factory.newDocumentBuilder();
114      builder.setErrorHandler(this);
115      builder.setEntityResolver(this);
116
117      DOMImplementation dom =
118        builder.getDOMImplementation();
119      DocumentType docType = 
120        dom.createDocumentType("family-tree", publicID, systemID);
121      doc = dom.createDocument(null, "family-tree", docType);
122
123    } catch (ParserConfigurationException ex) {
124      String s = "Illconfigured XML parser";
125      throw new FamilyTreeException(s, ex);
126
127    } catch (DOMException ex) {
128      String s = "While creating XML Document";
129      throw new FamilyTreeException(s, ex);
130    }
131    
132    // Keep track of all of the marriages
133    Set<Marriage> marriages = new HashSet<Marriage>();
134
135    // Construct the DOM tree
136    try {
137      // Make the family tree
138      Element ft = doc.getDocumentElement();
139
140      // Make the people
141      Iterator people = tree.getPeople().iterator();
142      while (people.hasNext()) {
143        Person person = (Person) people.next();
144        
145        // Create the person element
146        Element p = doc.createElement("person");
147        p.setAttribute("id", Integer.toString(person.getId()));
148        p.setAttribute("gender", 
149                       (person.getGender() == Person.MALE ? "male" :
150                        "female"));
151        ft.appendChild(p);
152
153        String firstName = person.getFirstName();
154        if(firstName != null) {
155          Element fn = doc.createElement("first-name");
156          p.appendChild(fn);
157          fn.appendChild(doc.createTextNode(firstName));
158        }
159
160        String middleName = person.getMiddleName();
161        if(middleName != null) {
162          Element mn = doc.createElement("middle-name");
163          p.appendChild(mn);
164          mn.appendChild(doc.createTextNode(middleName));
165        }
166
167        String lastName = person.getLastName();
168        if(lastName != null) {
169          Element ln = doc.createElement("last-name");
170          p.appendChild(ln);
171          ln.appendChild(doc.createTextNode(lastName));
172        }
173
174        Date dob = person.getDateOfBirth();
175        if(dob != null) {
176          Element d = doc.createElement("dob");
177          p.appendChild(d);
178          d.appendChild(getDateElement(doc, dob));
179        }
180
181        Date dod = person.getDateOfDeath();
182        if(dod != null) {
183          Element d = doc.createElement("dod");
184          p.appendChild(d);
185          d.appendChild(getDateElement(doc, dod));
186        }
187
188        Person father = person.getFather();
189        if(father != null) {
190          Element f = doc.createElement("father-id");
191          p.appendChild(f);
192          f.appendChild(doc.createTextNode(String.valueOf(father.getId())));
193        }
194
195        Person mother = person.getMother();
196        if(mother != null) {
197          Element m = doc.createElement("mother-id");
198          p.appendChild(m);
199          m.appendChild(doc.createTextNode(String.valueOf(mother.getId())));
200        }
201
202        // Make note of all marriages
203        marriages.addAll(person.getMarriages());
204        
205      }
206
207      // Make the marriages
208      Iterator iter = marriages.iterator();
209      while (iter.hasNext()) {
210        Marriage marriage = (Marriage) iter.next();
211        
212        Element m = doc.createElement("marriage");
213        m.setAttribute("husband-id",
214                       String.valueOf(marriage.getHusband().getId()));
215        m.setAttribute("wife-id",
216                       String.valueOf(marriage.getWife().getId()));
217        ft.appendChild(m);
218        
219        Date date = marriage.getDate();
220        if(date != null) {
221          m.appendChild(getDateElement(doc, date));
222        }
223
224        String location = marriage.getLocation();
225        if(location != null) {
226          Element l = doc.createElement("location");
227          m.appendChild(l);
228          l.appendChild(doc.createTextNode(location));
229        }
230
231      }
232
233    } catch (DOMException ex) {
234      String s = "** Exception while building DOM tree";
235      throw new FamilyTreeException(s, ex);
236    }
237
238    // Then we simply write the DOM tree to the destination
239    try {
240      Source src = new DOMSource(doc);
241      Result res = new StreamResult(this.pw);
242
243      TransformerFactory xFactory = TransformerFactory.newInstance();
244      Transformer xform = xFactory.newTransformer();
245      xform.setOutputProperty(OutputKeys.INDENT, "yes");
246      xform.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemID);
247      xform.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicID);
248
249      // Set stupid internal-xalan property to get a non-zero indent.
250      // Or modify output_xml.properties in $JAVA_HOME/jre/lib/rt.jar
251      xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
252
253      // Suppress warnings about "Declared encoding not matching
254      // actual one
255      xform.setOutputProperty(OutputKeys.ENCODING, "ASCII");
256      xform.transform(src, res);
257
258    } catch (TransformerException ex) {
259      String s = "While transforming XML";
260      throw new FamilyTreeException(s, ex);
261    }
262
263    this.pw.flush();
264  }
265  
266
267  /**
268   * Test program.  Create a simple family tree and dump it to the
269   * specified file or standard out if no file is specified.
270   */
271  public static void main(String[] args) {
272    // Make a family tree and dump it
273
274    // Make some people
275    Person me = PersonMain.me();
276    Person mom = PersonMain.mom(me);
277    Person dad = PersonMain.dad(me);
278
279    me.setMother(mom);
280    me.setFather(dad);
281
282    Marriage marriage = new Marriage(dad, mom);
283    marriage.setLocation("Durham, NH");
284
285    try {
286      DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);
287      marriage.setDate(df.parse("Jul 12, 1969"));
288
289    } catch (ParseException ex) {
290      System.out.println("** Malformed marriage date?");
291      System.exit(1);
292    }
293
294    mom.addMarriage(marriage);
295    dad.addMarriage(marriage);
296
297    // Create a family tree.  Add people in an interesting order.
298    FamilyTree tree = new FamilyTree();
299    tree.addPerson(dad);
300    tree.addPerson(mom);
301    tree.addPerson(me);
302
303    // Dump the family tree 
304    XmlDumper dumper = null;
305    if (args.length > 0) {
306      try {
307        dumper = new XmlDumper(args[0]);
308
309      } catch (IOException ex) {
310        err.println("** IOException: " + ex);
311        System.exit(1);
312      }
313
314    } else {
315      PrintWriter out = new PrintWriter(System.out, true);
316      dumper = new XmlDumper(out);
317    }
318
319    dumper.dump(tree);
320  }
321}