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