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