001/*
002// $Id: //open/util/resgen/src/org/eigenbase/xom/ElementDef.java#5 $
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, 6 November, 2000
024*/
025
026package org.eigenbase.xom;
027
028import java.io.*;
029import java.lang.reflect.Constructor;
030import java.lang.reflect.Field;
031import java.lang.reflect.InvocationTargetException;
032import java.util.*;
033
034/**
035 * ElementDef is the base class for all element definitions.  It specifies the
036 * basic interface as well as provides useful services for all elements.
037 **/
038public abstract class ElementDef implements NodeDef, Serializable, Cloneable
039{
040
041    /**
042     * getElementClass is a static helper function which finds the XMLDef class
043     * corresponding to an Element.  The Element's tag must start with the
044     * given prefix name, and
045     * the remainder of the tag must correspond to a static inner class of
046     * the given enclosing class.
047     * @param wrapper the DOMWrapper whose class to look up.
048     * @param enclosure a Class which encloses the Class to lookup.
049     * @param prefix a prefix which must appear on the tag name.
050     * @return the ElementDef Class corresponding to the element, or null if no
051     * class could be found (possible if this is a String element.
052     */
053    public static Class getElementClass(DOMWrapper wrapper,
054                                        Class enclosure,
055                                        String prefix)
056        throws XOMException
057    {
058        if (enclosure == null) {
059            // don't try to find a class -- they will use GenericDef
060            return null;
061        }
062        // wrapper must be of ELEMENT type.  If not, throw a XOMException.
063        if (wrapper.getType() != DOMWrapper.ELEMENT) {
064            throw new XOMException("DOMWrapper must be of ELEMENT type.");
065        }
066        // Retrieve the tag name.  It must start with the prefix.
067        String tag = wrapper.getTagName();
068        if (prefix == null) {
069            prefix = "";
070        } else if (!tag.startsWith(prefix)) {
071            throw new XOMException(
072                "Element names must start "
073                    + "\"" + prefix + "\": "
074                + tag + " is invalid.");
075        }
076
077        // Remove the prefix and look for the name in the _elements field
078        // of the enclosure class.  Note that the lookup is case-sensitive
079        // even though XML tags are not.
080        String className = tag.substring(prefix.length(), tag.length());
081        className = XOMUtil.capitalize(className);
082        Class elemClass = null;
083        try {
084            elemClass = Class.forName(enclosure.getName() + "$"
085                                      + className);
086        } catch (ClassNotFoundException ex) {
087            return null;
088        }
089        return elemClass;
090    }
091
092    /**
093     * constructElement is a helper function which builds the appropriate type
094     * of ElementDef from an XML Element.  This version of the function takes
095     * an Element and a Class object specifying the exact class to use in
096     * constructing the element.
097     *
098     * @param wrapper the DOM Element wrapper from which to build this class.
099     * @param elemClass the Class to use to construct this class.  It must have
100     * a constructor which takes the Element type.
101     * @return a fully constructed ElementDef of the type specified by
102     * Class.
103     * @throws XOMException if clazz has no constructor which takes Element,
104     * or if construction fails.
105     */
106    public static NodeDef constructElement(DOMWrapper wrapper,
107                                           Class elemClass)
108        throws XOMException
109    {
110        // Find a constructor of this class which takes an "Element" object
111        Constructor[] constructors = elemClass.getDeclaredConstructors();
112        Constructor elemConstructor = null;
113        for (int i = 0; i < constructors.length; i++) {
114            Class[] params = constructors[i].getParameterTypes();
115            if (params.length == 1 && params[0] == DOMWrapper.class) {
116                elemConstructor = constructors[i];
117                break;
118            }
119        }
120        if (elemConstructor == null) {
121            throw new XOMException(
122                "No constructor taking class DOMWrapper "
123                    + "could be found in class "
124                    + elemClass.getName());
125        }
126
127        // Call the constructor to instantiate the object
128        Object[] args = new Object[1];
129        args[0] = wrapper;
130        try {
131            return (ElementDef)(elemConstructor.newInstance(args));
132        } catch (InstantiationException ex) {
133            throw new XOMException("Unable to instantiate object of class "
134                                      + elemClass.getName() + ": "
135                                      + ex.getMessage());
136        } catch (InvocationTargetException ex) {
137            // the Element constructor can only throw XOMException or
138            // RuntimeException or Error, so cast to whichever type is appropriate
139            // and throw here.
140            Throwable target = ex.getTargetException();
141            if (target instanceof XOMException) {
142                throw (XOMException) target;
143            } else if (target instanceof RuntimeException) {
144                throw (RuntimeException) target;
145            } else if (target instanceof Error) {
146                throw (Error) target;
147            } else {
148                throw new XOMException(
149                    "Unexpected exception while "
150                        + "instantiating object: "
151                        + target.toString());
152            }
153        } catch (IllegalAccessException ex) {
154            throw new XOMException("Unable to instantiate object of class "
155                                      + elemClass.getName() + ": "
156                                      + ex.getMessage());
157        }
158    }
159
160    /**
161     * constructElement is a helper function which builds the appropriate type
162     * of ElementDef from an XML Element.  This function should be used when
163     * creating an ElementDef from a list of optional XML element types using
164     * ElementParser.requiredOption.  Generally, it is better to call the
165     * constructors of ElementDef subclasses directly if the exact type of
166     * an element is known.
167     * @param wrapper the DOM Element Wrapper from which to build this class.
168     * @return an ElementDef whose exact type depends on the tag name of the
169     * element definition.
170     * @throws XOMException if no subclass of ElementDef can be found,
171     * or if def is malformed.
172     */
173    public static NodeDef constructElement(
174        DOMWrapper wrapper, Class enclosure, String prefix)
175        throws XOMException
176    {
177        switch (wrapper.getType()) {
178        case DOMWrapper.ELEMENT:
179            Class elemClass = getElementClass(wrapper, enclosure, prefix);
180            if (elemClass == null) {
181                if (true) {
182                    return new WrapperElementDef(wrapper, enclosure, prefix);
183                } else {
184                    throw new XOMException("No class corresponding to element "
185                                           + wrapper.getTagName()
186                                           + " could be found in enclosure "
187                                           + enclosure.getName());
188                }
189            } else {
190                return constructElement(wrapper, elemClass);
191            }
192        case DOMWrapper.COMMENT:
193            return new CommentDef(wrapper.getText());
194        case DOMWrapper.CDATA:
195            return new CdataDef(wrapper.getText());
196        case DOMWrapper.FREETEXT:
197            return new TextDef(wrapper.getText());
198        default:
199            throw new XOMException("Unknown type: " + wrapper.getText());
200        }
201    }
202
203
204
205    // implement NodeDef
206    public void displayXML(XMLOutput out, int indent) {}
207
208    public void displayXML(XMLOutput out)
209    {
210        displayXML(out, 0);
211    }
212
213    /**
214     * The displayDiff function compares this element definition against another,
215     * compiling a message containing all diffs.  It is used internally by
216     * the equals(), diff(), and verifyEquals() functions.
217     * @param other the ElementDef to which to compare this element.
218     * @param out a PrintWriter to which to display any discovered differences,
219     * or null if just doing an equality check (and no diff report is needed).
220     * @param indent the current indentation level (used for nice display of diffs).
221     * @return true if this and other match exactly, false if not.
222     */
223    public boolean displayDiff(ElementDef other, PrintWriter out, int indent)
224    {
225        return false;
226    }
227
228    // implement NodeDef
229    public String getName()
230    {
231        return getClass().getName();
232    }
233
234    // implement NodeDef
235    public int getType()
236    {
237        return DOMWrapper.ELEMENT;
238    }
239
240    // implement NodeDef
241    public String getText()
242    {
243        return null;
244    }
245
246    /**
247     * This function writes an indentation level to the given PrintWriter.
248     * @param out the PrintWriter to which to write the indent.
249     * @param indent the indentation level
250     */
251    protected static void displayIndent(PrintWriter out, int indent)
252    {
253        for (int i = 0; i < indent; i++) {
254            out.print("   ");
255        }
256    }
257
258    /**
259     * This convenience function displays a String value with the given
260     * parameter name at the given indentation level.  It is meant to be
261     * called by subclasses of ElementDef.
262     * @param out the PrintWriter to which to write this String.
263     * @param name the parameter name of this string.
264     * @param value the value of the String parameter.
265     * @param indent the indentation level.
266     */
267    protected static void displayString(
268        PrintWriter out,
269        String name,
270        String value,
271        int indent)
272    {
273        displayIndent(out, indent);
274        if (value == null) {
275            out.println(name + ": null");
276        } else {
277            out.println(name + ": \"" + value + "\"");
278        }
279    }
280
281    /**
282     * This convenience function displays an XML attribute value
283     * with the given attribute name at the given indentation level.
284     * It should be called by subclasses of ElementDef.
285     * @param out the PrintWriter to which to write this String.
286     * @param name the attribute name.
287     * @param value the attribute value.
288     * @param indent the indentation level.
289     */
290    protected static void displayAttribute(
291        PrintWriter out,
292        String name,
293        Object value,
294        int indent)
295    {
296        displayIndent(out, indent);
297        if (value == null) {
298            out.println(name + " = null");
299        } else {
300            out.println(name + " = \"" + value.toString() + "\"");
301        }
302    }
303
304    /**
305     * This convenience function displays any ElementDef with the given
306     * parameter name at the given indentation level.
307     * @param out the PrintWriter to which to write this ElementDef.
308     * @param name the parameter name for this ElementDef.
309     * @param value the parameter's value (as an ElementDef).
310     * @param indent the indentation level.
311     */
312    protected static void displayElement(
313        PrintWriter out,
314        String name,
315        ElementDef value,
316        int indent)
317    {
318        displayIndent(out, indent);
319        if (value == null) {
320            out.println(name + ": null");
321        } else {
322            out.print(name + ": ");
323            value.display(out, indent);
324        }
325    }
326
327    /**
328     * This convenience function displays any array of ElementDef values with
329     * the given parameter name (assumed to represent an array) at the given
330     * indentation level.  Each value of the array will be written on a
331     * separate line with a new indentation.
332     * @param out the PrintWriter to which to write this ElementDef.
333     * @param name the parameter name for this ElementDef.
334     * @param values the parameter's values (as an ElementDef[] array).
335     * @param indent the indentation level.
336     */
337    protected static void displayElementArray(
338        PrintWriter out,
339        String name,
340        NodeDef[] values,
341        int indent)
342    {
343        displayIndent(out, indent);
344        if (values == null) {
345            out.println(name + ": null array");
346        } else {
347            out.println(name + ": array of " + values.length + " values");
348            for (int i = 0; i < values.length; i++) {
349                displayIndent(out, indent);
350                if (values[i] == null) {
351                    out.println(name + "[" + i + "]: null");
352                } else {
353                    out.print(name + "[" + i + "]: ");
354                    values[i].display(out, indent);
355                }
356            }
357        }
358    }
359
360    /**
361     * This convenience function displays any array of String values with
362     * the given parameter name (assumed to represent an array) at the given
363     * indentation level.  Each value of the array will be written on a
364     * separate line with a new indentation.
365     * @param out the PrintWriter to which to write this ElementDef.
366     * @param name the parameter name for this ElementDef.
367     * @param values the parameter's values (as a String[] array).
368     * @param indent the indentation level.
369     */
370    protected static void displayStringArray(
371        PrintWriter out,
372        String name,
373        String[] values,
374        int indent)
375    {
376        displayIndent(out, indent);
377        if (values == null) {
378            out.println(name + ": null array");
379        } else {
380            out.println(name + ": array of " + values.length + " values");
381            for (int i = 0; i < values.length; i++) {
382                displayIndent(out, indent);
383                if (values[i] == null) {
384                    out.println(name + "[" + i + "]: null");
385                } else {
386                    out.println(name + "[" + i + "]: " + values[i]);
387                }
388            }
389        }
390    }
391
392    /**
393     * This convenience function displays a String value in XML.
394     * parameter name at the given indentation level.  It is meant to be
395     * called by subclasses of ElementDef.
396     * @param out XMLOutput class to which to generate XML.
397     * @param tag the Tag name of this String object.
398     * @param value the String value.
399     */
400    protected static void displayXMLString(
401        XMLOutput out,
402        String tag,
403        String value)
404    {
405        if (value != null) {
406            out.stringTag(tag, value);
407        }
408    }
409
410    /**
411     * This convenience function displays any ElementDef in XML.
412     * @param out the XMLOutput class to which to generate XML.
413     * @param value the ElementDef to display.
414     */
415    protected static void displayXMLElement(
416        XMLOutput out,
417        ElementDef value)
418    {
419        if (value != null) {
420            value.displayXML(out, 0);
421        }
422    }
423
424    /**
425     * This convenience function displays an array of ElementDef values in XML.
426     * @param out the XMLOutput class to which to generate XML.
427     * @param values the ElementDef to display.
428     */
429    protected static void displayXMLElementArray(
430        XMLOutput out,
431        NodeDef[] values)
432    {
433        if (values != null) {
434            for (int i = 0; i < values.length; i++) {
435                values[i].displayXML(out, 0);
436            }
437        }
438    }
439
440    /**
441     * This convenience function displays a String array in XML.
442     * @param out the XMLOutput class to which to generate XML.
443     * @param tag the tag name for the String elements.
444     * @param values the actual string values.
445     */
446    protected static void displayXMLStringArray(
447        XMLOutput out,
448        String tag,
449        String[] values)
450    {
451        for (int i = 0; i < values.length; i++) {
452            out.stringTag(tag, values[i]);
453        }
454    }
455
456    /**
457     * This convenience function displays differences in two versions of
458     * the same string object.
459     * @param name the object name.
460     * @param value1 the first string.
461     * @param value2 the second string.
462     * @param out the PrintWriter to which to write differences.
463     * @param indent the indentation level.
464     * @return true if the strings match, false if not.
465     */
466    protected static boolean displayStringDiff(
467        String name,
468        String value1,
469        String value2,
470        PrintWriter out,
471        int indent)
472    {
473        // True if both values are null.
474        if (value1 == null && value2 == null) {
475            return true;
476        }
477        // Deal with the cases where one value is set but the other is not.
478        if (value2 == null) {
479            if (out != null) {
480                displayIndent(out, indent);
481                out.println("String " + name + ": mismatch: "
482                            + value1.toString() + " vs null.");
483            }
484            return false;
485        }
486        if (value1 == null) {
487            if (out != null) {
488                displayIndent(out, indent);
489                out.println("String " + name + ": mismatch: "
490                            + "null vs " + value2.toString() + ".");
491            }
492            return false;
493        }
494
495        // Finally, check the values themselves
496        if (value1.equals(value2)) {
497            return true;
498        }
499        if (out != null) {
500            displayIndent(out, indent);
501            out.println("String " + name + ": mismatch: "
502                        + value1.toString() + " vs "
503                        + value2.toString() + ".");
504        }
505        return false;
506    }
507
508    /**
509     * This convenience function displays differences in two versions of
510     * the same XML attribute value.
511     * @param name the attribute name.
512     * @param value1 the first attribute value.
513     * @param value2 the second attribute value.
514     * @param out the PrintWriter to which to write differences.
515     * @param indent the indentation level.
516     * @return true if the values match, false if not.
517     */
518    protected static boolean displayAttributeDiff(
519        String name,
520        Object value1,
521        Object value2,
522        PrintWriter out,
523        int indent)
524    {
525        // True if both values are null.
526        if (value1 == null && value2 == null) {
527            return true;
528        }
529        // Deal with the cases where one value is set but the other is not.
530        if (value2 == null) {
531            if (out != null) {
532                displayIndent(out, indent);
533                out.println("Attribute " + name + ": mismatch: "
534                            + value1.toString() + " vs null.");
535            }
536            return false;
537        }
538        if (value1 == null) {
539            if (out != null) {
540                displayIndent(out, indent);
541                out.println("Attribute " + name + ": mismatch: "
542                            + "null vs " + value2.toString() + ".");
543            }
544            return false;
545        }
546
547        // Verify that types match
548        if (value1.getClass() != value2.getClass()) {
549            if (out != null) {
550                displayIndent(out, indent);
551                out.println("Attribute " + name + ": class mismatch: "
552                            + value1.getClass().getName()
553                            + " vs "
554                            + value2.getClass().getName()
555                            + ".");
556            }
557            return false;
558        }
559
560        // Finally, check the values themselves
561        if (value1.equals(value2)) {
562            return true;
563        }
564        if (out != null) {
565            displayIndent(out, indent);
566            out.println("Attribute " + name + ": mismatch: "
567                        + value1.toString() + " vs "
568                        + value2.toString() + ".");
569        }
570        return false;
571    }
572
573    /**
574     * This convenience function displays differences in the values of any
575     * two ElementDefs, returning true if they match and false if not.
576     * @param name the object name.
577     * @param value1 the first value.
578     * @param value2 the second value.
579     * @param out the PrintWriter to which to write differences.
580     * @param indent the indentation level.
581     * @return true if the values match, false if not.
582     */
583    protected static boolean displayElementDiff(
584        String name,
585        NodeDef value1,
586        NodeDef value2,
587        PrintWriter out,
588        int indent)
589    {
590        // True if both values are null.
591        if (value1 == null && value2 == null) {
592            return true;
593        }
594        // Deal with the cases where one value is set but the other is not.
595        if (value2 == null) {
596            if (out != null) {
597                displayIndent(out, indent);
598                out.println("Object " + name + ": mismatch: "
599                            + "(...) vs null.");
600            }
601            return false;
602        }
603        if (value1 == null) {
604            if (out != null) {
605                displayIndent(out, indent);
606                out.println("Object " + name + ": mismatch: "
607                            + "null vs (...).");
608            }
609            return false;
610        }
611
612        // Verify that types match
613        if (value1.getClass() != value2.getClass()) {
614            if (out != null) {
615                displayIndent(out, indent);
616                out.println("Object " + name + ": class mismatch: "
617                            + value1.getClass().getName()
618                            + " vs "
619                            + value2.getClass().getName()
620                            + ".");
621            }
622            return false;
623        }
624
625        // Do a sub equality check
626        return ((ElementDef) value1).displayDiff(
627            (ElementDef) value2, out, indent);
628    }
629
630    /**
631     * This convenience function diffs any array of ElementDef values with
632     * the given array name.  All differences are written to the given
633     * PrintWriter at the given indentation level.
634     * @param name the array name.
635     * @param values1 the first array.
636     * @param values2 the second array.
637     * @param out the PrintWriter to which to write differences.
638     * @param indent the indentation level.
639     * @return true if the both arrays match, false if there are any differences.
640     */
641    protected static boolean displayElementArrayDiff(
642        String name,
643        NodeDef[] values1,
644        NodeDef[] values2,
645        PrintWriter out, int indent)
646    {
647        int length1 = 0;
648        int length2 = 0;
649        if (values1 != null) {
650            length1 = values1.length;
651        }
652        if (values2 != null) {
653            length2 = values2.length;
654        }
655        // Check array sizes
656        //  a null array does not differ from an empty array
657        if (length1 != length2) {
658            if (out != null) {
659                displayIndent(out, indent);
660                out.println("Array " + name + ": size mismatch: "
661                            + length1 + " vs "
662                            + length2 + ".");
663            }
664            return false;
665        }
666
667        // Check each member of the array
668        boolean diff = true;
669        for (int i = 0; i < length1; i++) {
670            diff = diff
671                && displayElementDiff(
672                    name + "[" + i + "]",
673                    values1[i], values2[i],
674                    out, indent);
675        }
676        return diff;
677    }
678
679    /**
680     * This convenience function diffs any array of strings with
681     * the given array name.  All differences are written to the given
682     * PrintWriter at the given indentation level.
683     * @param name the array name.
684     * @param values1 the first array.
685     * @param values2 the second array.
686     * @param out the PrintWriter to which to write differences.
687     * @param indent the indentation level.
688     * @return true if the both arrays match, false if there are any differences.
689     */
690    protected static boolean displayStringArrayDiff(
691        String name,
692        String[] values1,
693        String[] values2,
694        PrintWriter out,
695        int indent)
696    {
697        // Check array sizes
698        if (values1.length != values2.length) {
699            if (out != null) {
700                displayIndent(out, indent);
701                out.println("Array " + name + ": size mismatch: "
702                            + values1.length + " vs "
703                            + values2.length + ".");
704            }
705            return false;
706        }
707
708        // Check each member of the array
709        boolean diff = true;
710        for (int i = 0; i < values1.length; i++) {
711            diff = diff
712                && displayStringDiff(
713                    name + "[" + i + "]",
714                    values1[i], values2[i],
715                    out, indent);
716        }
717        return diff;
718    }
719
720    /**
721     * The toString function automatically uses display() to produce a string
722     * version of this ElementDef.  The indentation level is always zero.
723     */
724    public String toString()
725    {
726        StringWriter strOut = new StringWriter();
727        PrintWriter prOut = new PrintWriter(strOut);
728        display(prOut, 0);
729        return strOut.toString();
730    }
731
732    /**
733     * The toXML function automatically uses displayXML() to produce an XML
734     * version of this ElementDef as a String.
735     * @return an XML representation of this ElementDef, as a String.
736     */
737    public String toXML()
738    {
739        StringWriter writer = new StringWriter();
740        XMLOutput out = new XMLOutput(writer);
741        displayXML(out, 0);
742        return writer.toString();
743    }
744
745    /**
746     * The toCompactXML function automatically uses displayXML() to produce an XML
747     * version of this ElementDef as a String.  The generated XML is
748     * <i>compact</i>; free of unnecessary whitespace.  Compact XML is useful
749     * when embedding XML in a CDATA section or transporting over the network.
750     * @return an XML representation of this ElementDef, as a String.
751     */
752    public String toCompactXML()
753    {
754        StringWriter writer = new StringWriter();
755        XMLOutput out = new XMLOutput(writer);
756        out.setCompact(true);
757        displayXML(out, 0);
758        return writer.toString();
759    }
760
761    /**
762     * The diff function compares this element against another, determining if
763     * they are exactly equal.  If so, the function returns null.  If not,
764     * it returns a String describing the differences.
765     */
766    public String diff(ElementDef other)
767    {
768        StringWriter writer = new StringWriter();
769        PrintWriter out = new PrintWriter(writer);
770        boolean diff = displayDiff(other, out, 0);
771        if (!diff) {
772            return writer.toString();
773        } else {
774            return null;
775        }
776    }
777
778    /**
779     * Determines if this ElementDef is equal to other (deeply), returning true
780     * if the two are equal.
781     * @return true if this equals other, false if not.
782     * @throws ClassCastException if other is not an ElementDef.
783     */
784    public boolean equals(Object other)
785    {
786        try {
787            return displayDiff((ElementDef)other, null, 0);
788        } catch (ClassCastException ex) {
789            return false;
790        }
791    }
792
793    /**
794     * Returns a unique hash of this instance.
795     * @return hash of the toXML() return value
796     */
797    public int hashCode()
798    {
799        return this.toXML().hashCode();
800    }
801
802    /**
803     * Verifies that this ElementDef is equal to other, throwing a
804     * XOMException with a lengthy explanation if equality
805     * fails.
806     * @param other the ElementDef to compare to this one.
807     */
808    public void verifyEqual(ElementDef other)
809        throws XOMException
810    {
811        StringWriter writer = new StringWriter();
812        PrintWriter out = new PrintWriter(writer);
813        out.println();
814        boolean diff = displayDiff(other, out, 1);
815        out.println();
816        if (!diff) {
817            throw new XOMException(
818                "Element definition mismatch: "
819                    + writer.toString());
820        }
821    }
822
823    /**
824     * Clone an ElementDef.  Because all ElementDefs are serializable, we can
825     * clone through a memory buffer.
826     */
827    protected Object clone()
828        throws CloneNotSupportedException
829    {
830        try {
831            return deepCopy();
832        } catch (XOMException ex) {
833            throw new CloneNotSupportedException(
834                "Unable to clone " + getClass().getName() + ": "
835                + ex.toString());
836        }
837    }
838
839    /**
840     * Public version of clone(); returns a deep copy of this ElementDef.
841     */
842    public ElementDef deepCopy()
843        throws XOMException
844    {
845        try {
846            ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
847            ObjectOutputStream objOut = new ObjectOutputStream(byteBuffer);
848            objOut.writeObject(this);
849            objOut.flush();
850            ByteArrayInputStream byteIn = new
851                ByteArrayInputStream(byteBuffer.toByteArray());
852            ObjectInputStream objIn = new ObjectInputStream(byteIn);
853            ElementDef def = (ElementDef)(objIn.readObject());
854            return def;
855        } catch (IOException ex) {
856            throw new XOMException(ex, "Failed to serialize-copy ElementDef");
857        } catch (ClassNotFoundException ex) {
858            throw new XOMException(ex, "Failed to serialize-copy ElementDef");
859        }
860    }
861
862    // implement NodeDef
863    public DOMWrapper getWrapper()
864    {
865        try {
866            Field field = getClass().getField("_def");
867            return (DOMWrapper) field.get(this);
868        } catch (NoSuchFieldException ex) {
869            return null;
870        } catch (IllegalAccessException ex) {
871            throw new Error(ex.toString() + " in getWrapper");
872        }
873    }
874
875    // implement NodeDef
876    public NodeDef[] getChildren()
877    {
878        List childrenList = new ArrayList();
879        final Field[] fields = getClass().getFields();
880        for (int i = 0; i < fields.length; i++) {
881            Field field = fields[i];
882            try {
883                final Class type = field.getType();
884                if (NodeDef.class.isAssignableFrom(type)) {
885                    childrenList.add((NodeDef) field.get(this));
886                } else if (type.isArray()
887                    && NodeDef.class.isAssignableFrom(
888                    type.getComponentType())) {
889                    NodeDef[] nodes = (NodeDef[]) field.get(this);
890                    childrenList.addAll(Arrays.asList(nodes));
891                }
892            } catch (IllegalAccessException e) {
893                throw new RuntimeException(
894                    "Error while accessing field '" + field + "'", e);
895            }
896        }
897        return
898            (NodeDef[]) childrenList.toArray(new NodeDef[childrenList.size()]);
899    }
900
901    public void addChild(NodeDef child) throws XOMException
902    {
903        XOMUtil.addChild(this, child);
904    }
905
906    public void addChildren(NodeDef[] children) throws XOMException
907    {
908        XOMUtil.addChildren(this, children);
909    }
910
911    protected static NodeDef[] getMixedChildren_new(
912        DOMWrapper _def, Class clazz, String prefix) throws XOMException
913    {
914        DOMWrapper[] _elts = _def.getChildren();
915        int count = 0;
916        for (int i = 0; i < _elts.length; i++) {
917            switch (_elts[i].getType()) {
918            case DOMWrapper.ELEMENT:
919            case DOMWrapper.CDATA:
920            case DOMWrapper.COMMENT:
921                count++;
922                break;
923            case DOMWrapper.FREETEXT:
924            default:
925                break;
926            }
927        }
928        NodeDef[] children = new NodeDef[count];
929        count = 0;
930        for (int i = 0; i < _elts.length; i++) {
931            switch (_elts[i].getType()) {
932            case DOMWrapper.ELEMENT:
933            case DOMWrapper.CDATA:
934            case DOMWrapper.COMMENT:
935                children[count++] = constructElement(_elts[i], clazz, prefix);
936                break;
937            case DOMWrapper.FREETEXT:
938            default:
939                break;
940            }
941        }
942        return children;
943    }
944
945    protected static NodeDef[] getMixedChildren(
946        DOMWrapper _def, Class clazz, String prefix) throws XOMException
947    {
948        DOMWrapper[] _elts = _def.getChildren();
949        NodeDef[] children = new NodeDef[_elts.length];
950        for (int i = 0; i < _elts.length; i++) {
951            children[i] = constructElement(_elts[i], clazz, prefix);
952        }
953        return children;
954    }
955
956    protected static ElementDef[] getElementChildren(
957        DOMWrapper _def, Class clazz, String prefix) throws XOMException
958    {
959        DOMWrapper[] _elts = _def.getElementChildren();
960        ElementDef[] children = new ElementDef[_elts.length];
961        for (int i = 0; i < children.length; i++) {
962            children[i] = (ElementDef) constructElement(
963                _elts[i], clazz, prefix);
964        }
965        return children;
966    }
967
968    public Location getLocation() {
969        final DOMWrapper wrapper = getWrapper();
970        if (wrapper == null) {
971            return null;
972        } else {
973            return wrapper.getLocation();
974        }
975    }
976}
977
978// End ElementDef.java