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}