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
026package org.eigenbase.xom;
027
028import java.io.*;
029import java.lang.reflect.Constructor;
030import 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 */
040public 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