001 /* 002 // $Id: //open/util/resgen/src/org/eigenbase/xom/MetaTester.java#3 $ 003 // Package org.eigenbase.xom is an XML Object Mapper. 004 // Copyright (C) 2005-2005 The Eigenbase Project 005 // Copyright (C) 2005-2005 Disruptive Tech 006 // Copyright (C) 2005-2005 LucidEra, Inc. 007 // Portions Copyright (C) 2000-2005 Kana Software, Inc. and others. 008 // 009 // This library is free software; you can redistribute it and/or modify it 010 // under the terms of the GNU Lesser General Public License as published by the 011 // Free Software Foundation; either version 2 of the License, or (at your 012 // option) any later version approved by The Eigenbase Project. 013 // 014 // This library is distributed in the hope that it will be useful, 015 // but WITHOUT ANY WARRANTY; without even the implied warranty of 016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 // GNU Lesser General Public License for more details. 018 // 019 // You should have received a copy of the GNU Lesser General Public License 020 // along with this library; if not, write to the Free Software 021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 022 // 023 // dsommerfield, 28 December, 2000 024 */ 025 026 package org.eigenbase.xom; 027 028 import java.io.*; 029 import java.lang.reflect.Constructor; 030 import java.lang.reflect.InvocationTargetException; 031 032 /** 033 * The MetaTester class is a utility class for testing generated models. 034 * The tester reads a model file in XML, validates it against its DTD, 035 * converts it to its corresponding model definition class (always a 036 * subclass of ElementDef), and displays the results. 037 * The MetaTester may be used to test a model against a suite of input 038 * files to verify the model's correctness. 039 */ 040 public class MetaTester { 041 042 // rootDef is the ElementDef class representing the root of the model. 043 private Class rootDef; 044 045 // rootConstructor is the constructor for the rootDef class which 046 // takes Element as its only argument. 047 private Constructor rootConstructor; 048 049 /** The parser. */ 050 private Parser parser; 051 052 // model is the root of the metamodel, and contains all basic model 053 // information. 054 private MetaDef.Model model; 055 056 // modelDocType is the DocType expected for all test files 057 private String modelDocType; 058 059 /** 060 * The type of parser to use. Values are {@link XOMUtil#MSXML}, etc. 061 **/ 062 private int parserType; 063 064 /** 065 * Constructs a new MetaTester using the given model file, the given 066 * test file, and the directory containing all support files. 067 * @param modelFile an XML file describing the model to be tested. 068 * This model should have already been compiled using the MetaGenerator 069 * utility. 070 * @param fileDirectory the directory containing all output files 071 * (Java classes, dtds, etc) from the model compilation. The model 072 * and its associated java class must be compiled. 073 * @throws XOMException if the model file is corrupted or if any 074 * of its compiled components cannot be loaded. 075 */ 076 public MetaTester(String modelFile, 077 String fileDirectory, 078 int parserType) 079 throws XOMException, IOException 080 { 081 // Set the parser 082 this.parserType = parserType; 083 084 // Load the input model file. 085 FileInputStream in = null; 086 try { 087 in = new FileInputStream(modelFile); 088 } catch (IOException ex) { 089 throw new XOMException("Loading of model file " + modelFile 090 + " failed: " + ex.getMessage()); 091 } 092 093 // Parse the meta model. 094 Parser parser = XOMUtil.createDefaultParser(); 095 try { 096 DOMWrapper def = parser.parse(in); 097 model = new MetaDef.Model(def); 098 } catch (XOMException ex) { 099 throw new XOMException( 100 ex, "Failed to parse XML file: " + modelFile); 101 } 102 103 // Load the root java class of the Java version of this model. 104 // Then find the Constructor which takes a single DOMWrapper. 105 String modelRoot = getModelRoot(model); 106 try { 107 rootDef = Class.forName(model.className + "$" + modelRoot); 108 Class[] params = new Class[1]; 109 params[0] = DOMWrapper.class; 110 rootConstructor = rootDef.getConstructor(params); 111 } catch (ClassNotFoundException ex) { 112 throw new XOMException("Model class " + model.className 113 + "." + modelRoot + " could not be " 114 + "loaded: " + ex.getMessage()); 115 } catch (NoSuchMethodException ex) { 116 throw new XOMException("Model class " + model.className 117 + "." + modelRoot + " has no " 118 + "constructor which takes a " 119 + "DOMWrapper."); 120 } 121 122 // Figure out if the model uses plugins or imports by looking at all 123 // element definitions. If plugins or imports are in use, we can't use the 124 // dtd for validation. 125 boolean usesPlugins = false; 126 for(int i=0; i<model.elements.length; i++) { 127 if(model.elements[i] instanceof MetaDef.Plugin || 128 model.elements[i] instanceof MetaDef.Import) { 129 usesPlugins = true; 130 break; 131 } 132 } 133 134 // Construct parser for test documents. The exact parser we use 135 // depends on the setting of the parser variable. 136 // Use validation only if plugins are not used by the model. 137 modelDocType = null; 138 if (usesPlugins) { 139 System.out.println("Plugins or imports are in use: ignoring DTD."); 140 } else { 141 modelDocType = getModelDocType(model); 142 System.out.println("No plugins or imports: using DTD with DocType " 143 + modelDocType + "."); 144 } 145 146 parser = XOMUtil.makeParser( 147 parserType, usesPlugins, fileDirectory, 148 model.dtdName, modelDocType); 149 } 150 151 /** 152 * Helper function to copy from a reader to a writer 153 */ 154 private static void readerToWriter(Reader reader, Writer writer) 155 throws IOException 156 { 157 int numChars; 158 final int bufferSize = 16384; 159 char[] buffer = new char[bufferSize]; 160 while((numChars = reader.read(buffer)) != -1) { 161 if(numChars > 0) 162 writer.write(buffer, 0, numChars); 163 } 164 } 165 166 /** 167 * This helper function retrieves the root element name from a model. The 168 * root element name may be defined explicitly, or it may need to be 169 * located as the first element in the file itself. 170 * Also, if a prefix is defined, we need to add it here. 171 */ 172 private static String getModelRoot(MetaDef.Model model) 173 throws XOMException 174 { 175 if(model.root != null) 176 return model.root; 177 for(int i=0; i<model.elements.length; i++) { 178 if(model.elements[i] instanceof MetaDef.Element) { 179 return ((MetaDef.Element)model.elements[i]).type; 180 } 181 } 182 183 throw new XOMException("Model " + model.name + " has no " 184 + "root element defined and has no first " 185 + "element."); 186 } 187 188 /** 189 * This helper function retrieves the root dtd element name from a model. 190 * This is identical to the model root returned by getModelRoot, except 191 * that the prefix (if any) is prepended. If the root element has 192 * a dtdName defined, this will be used instead of the prefixed name. 193 */ 194 private static String getModelDocType(MetaDef.Model model) 195 throws XOMException 196 { 197 if(model.root != null) 198 return model.root; 199 for(int i=0; i<model.elements.length; i++) { 200 if(model.elements[i] instanceof MetaDef.Element) { 201 MetaDef.Element elt = (MetaDef.Element)(model.elements[i]); 202 if(model.root == null || 203 model.root.equals(elt.type)) { 204 if(elt.dtdName != null) 205 return elt.dtdName; 206 else if(model.prefix != null) 207 return model.prefix + elt.type; 208 else 209 return elt.type; 210 } 211 } 212 } 213 214 if(model.root == null) 215 throw new XOMException("Model " + model.name + " has no " 216 + "root element defined and has no first " 217 + "element."); 218 else 219 throw new XOMException("Model root element " + model.root 220 + " is not defined as an Element."); 221 } 222 223 /** 224 * Instantiate the Element into an ElementDef of the correct type. 225 */ 226 private ElementDef instantiate(DOMWrapper elt) 227 throws XOMException 228 { 229 ElementDef def = null; 230 try { 231 Object[] args = new Object[1]; 232 args[0] = elt; 233 def = (ElementDef)(rootConstructor.newInstance(args)); 234 } catch (InstantiationException ex) { 235 throw new XOMException("Unable to instantiate holder class " 236 + rootDef.getName() + ": " 237 + ex.getMessage()); 238 } catch (IllegalAccessException ex) { 239 throw new XOMException("Unable to instantiate holder class " 240 + rootDef.getName() + ": " 241 + ex.getMessage()); 242 } catch (InvocationTargetException ex) { 243 Throwable sub = ex.getTargetException(); 244 if(sub instanceof RuntimeException) 245 throw (RuntimeException)sub; 246 else if(sub instanceof XOMException) 247 throw (XOMException)sub; 248 else 249 throw new XOMException("Exeception occurred while " 250 + "instantiating holder class " 251 + rootDef.getName() + ": " 252 + sub.toString()); 253 } 254 return def; 255 } 256 257 /** 258 * Tests a specific instance of the given model, as described by 259 * testFile. Testing includes parsing testFile, validating against 260 * its associated dtd, and converting to its assocated java class. 261 * The contents of the java class are displayed to complete the test. 262 * @param testFile the XML file to be tested. 263 * @param fileDirectory directory containing files. 264 * @throws XOMException if the test fails for any reason. 265 */ 266 public void testFile(String testFile, String fileDirectory) 267 throws XOMException 268 { 269 // Set a FILE url for the DTD, if one was provided 270 File dtdPath = new File(fileDirectory, model.dtdName); 271 String dtdUrl = "file:" + dtdPath.getAbsolutePath(); 272 273 // Read the file into a String. Do so to avoid the complexity of 274 // parsing directly from an input stream (rather than a reader). 275 // Add an XML declaration and DocType here, unless we're using 276 // MSXML. 277 String xmlString = null; 278 try { 279 StringWriter sWriter = new StringWriter(); 280 FileReader reader = new FileReader(testFile); 281 282 if(parserType != XOMUtil.MSXML) { 283 PrintWriter out = new PrintWriter(sWriter); 284 out.println("<?xml version=\"1.0\" ?>"); 285 if(modelDocType != null) 286 out.println("<!DOCTYPE " + modelDocType 287 + " SYSTEM \"" + dtdUrl + "\">"); 288 out.flush(); 289 } 290 291 readerToWriter(reader, sWriter); 292 reader.close(); 293 xmlString = sWriter.toString(); 294 } catch (IOException ex) { 295 throw new XOMException("Unable to read input test " 296 + testFile + ": " + ex.getMessage()); 297 } 298 299 DOMWrapper elt = parser.parse(xmlString); 300 301 // Instantiate the ElementDef class using its Element constructor. 302 ElementDef def = instantiate(elt); 303 304 // Display the results 305 System.out.println("Testing model " + testFile); 306 System.out.println("Display:"); 307 System.out.println(def.toString()); 308 System.out.println(); 309 310 // Display the results in XML as well 311 String xmlOut = def.toXML(); 312 System.out.println(); 313 System.out.println("Regurgitated XML:"); 314 System.out.println(xmlOut); 315 316 // Parse the generated XML back into another ElementDef. 317 // To do so, we must add the xml PI and DOCTYPE at the top of the 318 // String (unless we're using MSXML). 319 if(parserType != XOMUtil.MSXML) { 320 StringWriter writer = new StringWriter(); 321 PrintWriter out = new PrintWriter(writer); 322 out.println("<?xml version=\"1.0\" ?>"); 323 if(modelDocType != null) 324 out.println("<!DOCTYPE " + modelDocType 325 + " SYSTEM \"" + dtdUrl + "\">"); 326 out.println(xmlOut); 327 out.flush(); 328 xmlOut = writer.toString(); 329 } 330 DOMWrapper elt2 = parser.parse(xmlOut); 331 332 // Instantiate the second ElementDef class 333 ElementDef def2 = instantiate(elt2); 334 335 // Verify equality, and then test equality 336 try { 337 def.verifyEqual(def2); 338 } 339 catch(XOMException ex) { 340 System.err.println("Equality failure. Regurgitated XML:"); 341 System.err.println(xmlOut); 342 throw ex; 343 } 344 if(!def.equals(def2)) 345 throw new XOMException("Equality check failed even though " 346 + "verifyEqual passed."); 347 } 348 349 /** 350 * The MetaTester tests a suite of test model files against a 351 * compiled model. 352 * <p>Arguments: 353 * <ol> 354 * <li>The name of the model description file. This is an XML file 355 * describing the model itself. 356 * <li>The name of the output directory. This output directory should 357 * contain all files generated when compiling the model. 358 * </ol> 359 * <p>All other arguments are the names of the test model files. Each 360 * of these will be tested and displayed in turn. 361 */ 362 public static void main(String[] args) 363 throws XOMException, IOException 364 { 365 int firstArg = 0; 366 if(args.length > 0 && args[0].equals("-debug")) { 367 System.err.println("MetaTester pausing for debugging. " 368 + "Attach your debugger " 369 + "and press return."); 370 try { 371 System.in.read(); 372 firstArg++; 373 } 374 catch(IOException ex) { 375 // Do nothing 376 } 377 } 378 379 int parser = XOMUtil.MSXML; 380 if (firstArg < args.length && args[firstArg].equals("-msxml")) { 381 parser = XOMUtil.MSXML; 382 firstArg++; 383 } 384 else if (firstArg < args.length && args[firstArg].equals("-xerces")) { 385 parser = XOMUtil.XERCES; 386 firstArg++; 387 } 388 389 if(args.length < firstArg+2) { 390 System.err.println( 391 "Usage: java MetaTester [-debug] [-msxml | -xerces] " 392 + "<model XML file> <output dir> <tests> ..."); 393 System.exit(-1); 394 } 395 396 MetaTester tester = new MetaTester(args[0+firstArg], args[1+firstArg], 397 parser); 398 for(int i=2+firstArg; i<args.length; i++) 399 tester.testFile(args[i], args[1+firstArg]); 400 } 401 402 403 } 404 405 406 // End MetaTester.java