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