View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.xml;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.StringReader;
24  import java.lang.reflect.Array;
25  import java.lang.reflect.Constructor;
26  import java.lang.reflect.Field;
27  import java.lang.reflect.InvocationTargetException;
28  import java.lang.reflect.Method;
29  import java.lang.reflect.Modifier;
30  import java.net.InetAddress;
31  import java.net.MalformedURLException;
32  import java.net.URL;
33  import java.net.UnknownHostException;
34  import java.security.AccessController;
35  import java.security.PrivilegedAction;
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.HashMap;
39  import java.util.HashSet;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Locale;
43  import java.util.Map;
44  import java.util.Properties;
45  import java.util.Queue;
46  import java.util.ServiceLoader;
47  import java.util.Set;
48  import java.util.concurrent.atomic.AtomicReference;
49  
50  import org.eclipse.jetty.util.ArrayQueue;
51  import org.eclipse.jetty.util.LazyList;
52  import org.eclipse.jetty.util.Loader;
53  import org.eclipse.jetty.util.StringUtil;
54  import org.eclipse.jetty.util.TypeUtil;
55  import org.eclipse.jetty.util.component.LifeCycle;
56  import org.eclipse.jetty.util.log.Log;
57  import org.eclipse.jetty.util.log.Logger;
58  import org.eclipse.jetty.util.resource.Resource;
59  import org.xml.sax.InputSource;
60  import org.xml.sax.SAXException;
61  
62  /**
63   * <p>Configures objects from XML.</p>
64   * <p>This class reads an XML file conforming to the configure.dtd DTD
65   * and uses it to configure and object by calling set, put or other methods on the object.</p>
66   * <p>The actual XML file format may be changed (eg to spring XML) by implementing the
67   * {@link ConfigurationProcessorFactory} interface to be found by the
68   * {@link ServiceLoader} by using the DTD and first tag element in the file.
69   * Note that DTD will be null if validation is off.</p>
70   * <p>
71   * The configuration can be parameterised with properties that are looked up via the 
72   * Property XML element and set on the configuration via the map returned from 
73   * {@link #getProperties()}</p>
74   * <p>
75   * The configuration can create and lookup beans by ID.  If multiple configurations are used, then it
76   * is good practise to copy the entries from the {@link #getIdMap()} of a configuration to the next 
77   * configuration so that they can share an ID space for beans.</p>
78   */
79  public class XmlConfiguration
80  {
81      private static final Logger LOG = Log.getLogger(XmlConfiguration.class);
82      private static final Class<?>[] __primitives =
83              {Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE};
84      private static final Class<?>[] __boxedPrimitives =
85              {Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class};
86      private static final Class<?>[] __supportedCollections =
87              {ArrayList.class, ArrayQueue.class, HashSet.class, Queue.class, List.class, Set.class, Collection.class};
88      private static final Iterable<ConfigurationProcessorFactory> __factoryLoader = ServiceLoader.load(ConfigurationProcessorFactory.class);
89      private static final XmlParser __parser = initParser();
90      private static XmlParser initParser()
91      {
92          XmlParser parser = new XmlParser();
93          URL config60 = Loader.getResource(XmlConfiguration.class, "org/eclipse/jetty/xml/configure_6_0.dtd");
94          URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd");
95          URL config90 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_9_0.dtd");
96          URL config93 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_9_3.dtd");
97          parser.redirectEntity("configure.dtd",config90);
98          parser.redirectEntity("configure_1_0.dtd",config60);
99          parser.redirectEntity("configure_1_1.dtd",config60);
100         parser.redirectEntity("configure_1_2.dtd",config60);
101         parser.redirectEntity("configure_1_3.dtd",config60);
102         parser.redirectEntity("configure_6_0.dtd",config60);
103         parser.redirectEntity("configure_7_6.dtd",config76);
104         parser.redirectEntity("configure_9_0.dtd",config90);
105         parser.redirectEntity("configure_9_3.dtd",config93);
106 
107         parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config93);
108         parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config93);
109         parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config93);
110 
111         parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config93);
112         parser.redirectEntity("-//Jetty//Configure//EN",config93);
113 
114         return parser;
115     }
116 
117     private final Map<String, Object> _idMap = new HashMap<>();
118     private final Map<String, String> _propertyMap = new HashMap<>();
119     private final URL _url;
120     private final String _dtd;
121     private ConfigurationProcessor _processor;
122 
123     /**
124      * Reads and parses the XML configuration file.
125      *
126      * @param configuration the URL of the XML configuration
127      * @throws IOException if the configuration could not be read
128      * @throws SAXException if the configuration could not be parsed
129      */
130     public XmlConfiguration(URL configuration) throws SAXException, IOException
131     {
132         synchronized (__parser)
133         {
134             _url=configuration;
135             setConfig(__parser.parse(configuration.toString()));
136             _dtd=__parser.getDTD();
137         }
138     }
139 
140     /**
141      * Reads and parses the XML configuration string.
142      *
143      * @param configuration String of XML configuration commands excluding the normal XML preamble.
144      * The String should start with a "&lt;Configure ....&gt;" element.
145      * @throws IOException if the configuration could not be read
146      * @throws SAXException if the configuration could not be parsed
147      */
148     public XmlConfiguration(String configuration) throws SAXException, IOException
149     {
150         configuration = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE Configure PUBLIC \"-//Jetty//Configure//EN\" \"http://eclipse.org/jetty/configure.dtd\">"
151                 + configuration;
152         InputSource source = new InputSource(new StringReader(configuration));
153         synchronized (__parser)
154         {
155             _url=null;
156             setConfig( __parser.parse(source));
157             _dtd=__parser.getDTD();
158         }
159     }
160 
161     /**
162      * Reads and parses the XML configuration stream.
163      *
164      * @param configuration An input stream containing a complete configuration file
165      * @throws IOException if the configuration could not be read
166      * @throws SAXException if the configuration could not be parsed
167      */
168     public XmlConfiguration(InputStream configuration) throws SAXException, IOException
169     {
170         InputSource source = new InputSource(configuration);
171         synchronized (__parser)
172         {
173             _url=null;
174             setConfig(__parser.parse(source));
175             _dtd=__parser.getDTD();
176         }
177     }
178 
179     private void setConfig(XmlParser.Node config)
180     {
181         if ("Configure".equals(config.getTag()))
182         {
183             _processor=new JettyXmlConfiguration();
184         }
185         else if (__factoryLoader!=null)
186         {
187             for (ConfigurationProcessorFactory factory : __factoryLoader)
188             {
189                 _processor = factory.getConfigurationProcessor(_dtd, config.getTag());
190                 if (_processor!=null)
191                     break;
192             }
193 
194             if (_processor==null)
195                 throw new IllegalStateException("Unknown configuration type: "+config.getTag()+" in "+this);
196         }
197         else
198         {
199             throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
200         }
201         _processor.init(_url,config,this);
202     }
203 
204     /* ------------------------------------------------------------ */
205     /** Get the map of ID String to Objects that is used to hold
206      * and lookup any objects by ID.  
207      * <p>
208      * A New, Get or Call XML element may have an
209      * id attribute which will cause the resulting object to be placed into
210      * this map.  A Ref XML element will lookup an object from this map.</p>
211      * <p>
212      * When chaining configuration files, it is good practise to copy the 
213      * ID entries from the ID map to the map of the next configuration, so
214      * that they may share an ID space
215      * </p>
216      *  
217      * @return A modifiable map of ID strings to Objects
218      */
219     public Map<String, Object> getIdMap()
220     {
221         return _idMap;
222     }
223 
224     /* ------------------------------------------------------------ */
225     /**
226      * Get the map of properties used by the Property XML element
227      * to parameterise configuration. 
228      * @return A modifiable map of properties.
229      */
230     public Map<String, String> getProperties()
231     {
232         return _propertyMap;
233     }
234 
235     /**
236      * Applies the XML configuration script to the given object.
237      *
238      * @param obj The object to be configured, which must be of a type or super type
239      * of the class attribute of the &lt;Configure&gt; element.
240      * @throws Exception if the configuration fails
241      * @return the configured object
242      */
243     public Object configure(Object obj) throws Exception
244     {
245         return _processor.configure(obj);
246     }
247 
248     /**
249      * Applies the XML configuration script.
250      * If the root element of the configuration has an ID, an object is looked up by ID and its type checked
251      * against the root element's type.
252      * Otherwise a new object of the type specified by the root element is created.
253      *
254      * @return The newly created configured object.
255      * @throws Exception if the configuration fails
256      */
257     public Object configure() throws Exception
258     {
259         return _processor.configure();
260     }
261     
262     /* ------------------------------------------------------------ */
263     /** Initialize a new Object defaults.
264      * <p>This method must be called by any {@link ConfigurationProcessor} when it 
265      * creates a new instance of an object before configuring it, so that a derived 
266      * XmlConfiguration class may inject default values.
267      * @param object the object to initialize defaults on
268      */
269     public void initializeDefaults(Object object)
270     {
271     }
272     
273 
274     private static class JettyXmlConfiguration implements ConfigurationProcessor
275     {
276         
277         private String _url;
278         XmlParser.Node _root;
279         XmlConfiguration _configuration;
280 
281         public void init(URL url, XmlParser.Node root, XmlConfiguration configuration)
282         {
283             _url=url==null?null:url.toString();
284             _root=root;
285             _configuration=configuration;
286         }
287 
288         public Object configure(Object obj) throws Exception
289         {
290             // Check the class of the object
291             Class<?> oClass = nodeClass(_root);
292             if (oClass != null && !oClass.isInstance(obj))
293             {
294                 String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
295                 throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders+" in "+_url);
296             }
297             String id=_root.getAttribute("id");
298             if (id!=null)
299                 _configuration.getIdMap().put(id,obj);
300             configure(obj,_root,0);
301             return obj;
302         }
303 
304         public Object configure() throws Exception
305         {
306             Class<?> oClass = nodeClass(_root);
307 
308             String id = _root.getAttribute("id");
309             Object obj = id == null?null:_configuration.getIdMap().get(id);
310 
311             int index = 0;
312             if (obj == null && oClass != null)
313             {
314                 index = _root.size();
315                 Map<String, Object> namedArgMap = new HashMap<>();
316 
317                 List<Object> arguments = new LinkedList<>();
318                 for (int i = 0; i < _root.size(); i++)
319                 {
320                     Object o = _root.get(i);
321                     if (o instanceof String)
322                     {
323                         continue;
324                     }
325                     XmlParser.Node node = (XmlParser.Node)o;
326 
327                     if (!(node.getTag().equals("Arg")))
328                     {
329                         index = i;
330                         break;
331                     }
332                     else
333                     {
334                         String namedAttribute = node.getAttribute("name");
335                         Object value=value(obj,(XmlParser.Node)o);
336                         if (namedAttribute != null)
337                             namedArgMap.put(namedAttribute,value);
338                         arguments.add(value);
339                     }
340                 }
341 
342                 try
343                 {
344                     if (namedArgMap.size() > 0)
345                         obj = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
346                     else
347                         obj = TypeUtil.construct(oClass, arguments.toArray());
348                 }
349                 catch (NoSuchMethodException x)
350                 {
351                     throw new IllegalStateException(String.format("No constructor %s(%s,%s) in %s",oClass,arguments,namedArgMap,_url));
352                 }
353             }
354             if (id!=null)
355                 _configuration.getIdMap().put(id,obj);
356                 
357             _configuration.initializeDefaults(obj);
358             configure(obj, _root, index);
359             return obj;
360         }
361 
362         private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
363         {
364             String className = node.getAttribute("class");
365             if (className == null)
366                 return null;
367 
368             return Loader.loadClass(XmlConfiguration.class,className);
369         }
370 
371         /**
372          * Recursive configuration routine.
373          * This method applies the nested Set, Put, Call, etc. elements to the given object.
374          *
375          * @param obj the object to configure
376          * @param cfg the XML nodes of the configuration
377          * @param i the index of the XML nodes
378          * @throws Exception if the configuration fails
379          */
380         public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
381         {
382             // Object already constructed so skip any arguments
383             for (; i < cfg.size(); i++)
384             {
385                 Object o = cfg.get(i);
386                 if (o instanceof String)
387                     continue;
388                 XmlParser.Node node = (XmlParser.Node)o;
389                 if ("Arg".equals(node.getTag()))
390                 {
391                     LOG.warn("Ignored arg: "+node);
392                     continue;
393                 }
394                 break;
395             }
396             
397             // Process real arguments
398             for (; i < cfg.size(); i++)
399             {
400                 Object o = cfg.get(i);
401                 if (o instanceof String)
402                     continue;
403                 XmlParser.Node node = (XmlParser.Node)o;
404 
405                 try
406                 {
407                     String tag = node.getTag();
408                     switch (tag)
409                     {
410                         case "Set":
411                             set(obj, node);
412                             break;
413                         case "Put":
414                             put(obj, node);
415                             break;
416                         case "Call":
417                             call(obj, node);
418                             break;
419                         case "Get":
420                             get(obj, node);
421                             break;
422                         case "New":
423                             newObj(obj, node);
424                             break;
425                         case "Array":
426                             newArray(obj, node);
427                             break;
428                         case "Map":
429                             newMap(obj,node);
430                             break;
431                         case "Ref":
432                             refObj(obj, node);
433                             break;
434                         case "Property":
435                             propertyObj(node);
436                             break;
437                         case "SystemProperty":
438                             systemPropertyObj(node);
439                             break;
440                         case "Env":
441                             envObj(node);
442                             break;
443                         default:
444                             throw new IllegalStateException("Unknown tag: " + tag + " in " + _url);
445                     }
446                 }
447                 catch (Exception e)
448                 {
449                     LOG.warn("Config error at " + node,e.toString()+" in "+_url);
450                     throw e;
451                 }
452             }
453         }
454 
455         /*
456          * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
457          * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
458          * construct a suitable value from original value. @param obj
459          *
460          * @param node
461          */
462         private void set(Object obj, XmlParser.Node node) throws Exception
463         {
464             String attr = node.getAttribute("name");
465             String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1);
466             Object value = value(obj,node);
467             Object[] arg =
468             { value };
469 
470             Class<?> oClass = nodeClass(node);
471             if (oClass != null)
472                 obj = null;
473             else
474                 oClass = obj.getClass();
475 
476             Class<?>[] vClass =
477             { Object.class };
478             if (value != null)
479                 vClass[0] = value.getClass();
480 
481             if (LOG.isDebugEnabled())
482                 LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
483 
484             // Try for trivial match
485             try
486             {
487                 Method set = oClass.getMethod(name,vClass);
488                 set.invoke(obj,arg);
489                 return;
490             }
491             catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
492             {
493                 LOG.ignore(e);
494             }
495 
496             // Try for native match
497             try
498             {
499                 Field type = vClass[0].getField("TYPE");
500                 vClass[0] = (Class<?>)type.get(null);
501                 Method set = oClass.getMethod(name,vClass);
502                 set.invoke(obj,arg);
503                 return;
504             }
505             catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
506             {
507                 LOG.ignore(e);
508             }
509 
510             // Try a field
511             try
512             {
513                 Field field = oClass.getField(attr);
514                 if (Modifier.isPublic(field.getModifiers()))
515                 {
516                     field.set(obj,value);
517                     return;
518                 }
519             }
520             catch (NoSuchFieldException e)
521             {
522                 LOG.ignore(e);
523             }
524 
525             // Search for a match by trying all the set methods
526             Method[] sets = oClass.getMethods();
527             Method set = null;
528             for (int s = 0; sets != null && s < sets.length; s++)
529             {
530                 Class<?>[] paramTypes = sets[s].getParameterTypes();
531                 if (name.equals(sets[s].getName()) && paramTypes.length == 1)
532                 {
533                     // lets try it
534                     try
535                     {
536                         set = sets[s];
537                         sets[s].invoke(obj,arg);
538                         return;
539                     }
540                     catch (IllegalArgumentException | IllegalAccessException e)
541                     {
542                         LOG.ignore(e);
543                     }
544 
545                     try
546                     {
547                         for (Class<?> c : __supportedCollections)
548                             if (paramTypes[0].isAssignableFrom(c))
549                             {
550                                 sets[s].invoke(obj,convertArrayToCollection(value,c));
551                                 return;
552                             }
553                     }
554                     catch (IllegalAccessException e)
555                     {
556                         LOG.ignore(e);
557                     }
558                 }
559             }
560 
561             // Try converting the arg to the last set found.
562             if (set != null)
563             {
564                 try
565                 {
566                     Class<?> sClass = set.getParameterTypes()[0];
567                     if (sClass.isPrimitive())
568                     {
569                         for (int t = 0; t < __primitives.length; t++)
570                         {
571                             if (sClass.equals(__primitives[t]))
572                             {
573                                 sClass = __boxedPrimitives[t];
574                                 break;
575                             }
576                         }
577                     }
578                     Constructor<?> cons = sClass.getConstructor(vClass);
579                     arg[0] = cons.newInstance(arg);
580                     _configuration.initializeDefaults(arg[0]);
581                     set.invoke(obj,arg);
582                     return;
583                 }
584                 catch (NoSuchMethodException | IllegalAccessException | InstantiationException e)
585                 {
586                     LOG.ignore(e);
587                 }
588             }
589 
590             // No Joy
591             throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
592         }
593 
594         /**
595          * @param array the array to convert
596          * @param collectionType the desired collection type
597          * @return a collection of the desired type if the array can be converted
598          */
599         private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
600         {
601             Collection<?> collection = null;
602             if (array.getClass().isArray())
603             {
604                 if (collectionType.isAssignableFrom(ArrayList.class))
605                     collection = convertArrayToArrayList(array);
606                 else if (collectionType.isAssignableFrom(HashSet.class))
607                     collection = new HashSet<>(convertArrayToArrayList(array));
608                 else if (collectionType.isAssignableFrom(ArrayQueue.class))
609                 {
610                     ArrayQueue<Object> q= new ArrayQueue<>();
611                     q.addAll(convertArrayToArrayList(array));
612                     collection=q;
613                 }
614             }
615             if (collection==null)
616                 throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
617             return collection;
618         }
619 
620         private static ArrayList<Object> convertArrayToArrayList(Object array)
621         {
622             int length = Array.getLength(array);
623             ArrayList<Object> list = new ArrayList<>(length);
624             for (int i = 0; i < length; i++)
625                 list.add(Array.get(array,i));
626             return list;
627         }
628 
629         /*
630          * Call a put method.
631          *
632          * @param obj @param node
633          */
634         private void put(Object obj, XmlParser.Node node) throws Exception
635         {
636             if (!(obj instanceof Map))
637                 throw new IllegalArgumentException("Object for put is not a Map: " + obj);
638             @SuppressWarnings("unchecked")
639             Map<Object, Object> map = (Map<Object, Object>)obj;
640 
641             String name = node.getAttribute("name");
642             Object value = value(obj, node);
643             map.put(name,value);
644             if (LOG.isDebugEnabled())
645                 LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
646         }
647 
648         /*
649          * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node
650          *
651          * @return @exception Exception
652          */
653         private Object get(Object obj, XmlParser.Node node) throws Exception
654         {
655             Class<?> oClass = nodeClass(node);
656             if (oClass != null)
657                 obj = null;
658             else
659                 oClass = obj.getClass();
660 
661             String name = node.getAttribute("name");
662             String id = node.getAttribute("id");
663             if (LOG.isDebugEnabled())
664                 LOG.debug("XML get " + name);
665 
666             try
667             {
668                 // try calling a getXxx method.
669                 Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase(Locale.ENGLISH) + name.substring(1),(java.lang.Class[])null);
670                 obj = method.invoke(obj,(java.lang.Object[])null);
671                 if (id!=null)
672                     _configuration.getIdMap().put(id,obj);
673                 configure(obj,node,0);
674             }
675             catch (NoSuchMethodException nsme)
676             {
677                 try
678                 {
679                     Field field = oClass.getField(name);
680                     obj = field.get(obj);
681                     configure(obj,node,0);
682                 }
683                 catch (NoSuchFieldException nsfe)
684                 {
685                     throw nsme;
686                 }
687             }
688             return obj;
689         }
690 
691         /*
692          * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to
693          * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given
694          * class. i.e. we ignore any static methods in superclasses. @param obj
695          *
696          * @param node @return @exception Exception
697          */
698         private Object call(Object obj, XmlParser.Node node) throws Exception
699         {
700             AttrOrElementNode aoeNode=new AttrOrElementNode(obj,node,"Id","Name","Class","Arg");
701             String id = aoeNode.getString("Id");
702             String name = aoeNode.getString("Name");
703             String clazz = aoeNode.getString("Class");
704             List<Object> args = aoeNode.getList("Arg");
705             
706             
707             Class<?> oClass;
708             if (clazz!=null)
709             {
710                 // static call
711                 oClass=Loader.loadClass(XmlConfiguration.class,clazz);
712                 obj=null;
713             }
714             else if (obj!=null)
715             {
716                 oClass = obj.getClass();
717             }
718             else
719                 throw new IllegalArgumentException(node.toString());
720            
721             if (LOG.isDebugEnabled())
722                 LOG.debug("XML call " + name);
723 
724             try
725             {
726                 Object nobj= TypeUtil.call(oClass,name,obj,args.toArray(new Object[args.size()]));
727                 if (id != null)
728                     _configuration.getIdMap().put(id,nobj);
729                 configure(nobj,node,aoeNode.getNext());
730                 return nobj;
731             }
732             catch (NoSuchMethodException e)
733             {
734                 IllegalStateException ise = new IllegalStateException("No Method: " + node + " on " + oClass);
735                 ise.initCause(e);
736                 throw ise;
737             }
738         }
739 
740         /*
741          * Create a new value object.
742          *
743          * @param obj
744          * @param node
745          *
746          * @return @exception Exception
747          */
748         private Object newObj(Object obj, XmlParser.Node node) throws Exception
749         {
750             AttrOrElementNode aoeNode=new AttrOrElementNode(obj,node,"Id","Class","Arg");
751             String id = aoeNode.getString("Id");
752             String clazz = aoeNode.getString("Class");
753             List<XmlParser.Node> argNodes = aoeNode.getNodes("Arg");
754 
755             if (LOG.isDebugEnabled())
756                 LOG.debug("XML new " + clazz);
757             
758             Class<?> oClass = Loader.loadClass(XmlConfiguration.class,clazz);
759             
760             // Find the <Arg> elements
761             Map<String, Object> namedArgMap = new HashMap<>();
762             List<Object> arguments = new LinkedList<>();
763             for (XmlParser.Node child : argNodes)
764             {
765                 String namedAttribute = child.getAttribute("name");
766                 Object value=value(obj,child);
767                 if (namedAttribute != null)
768                 {
769                     // named arguments
770                     namedArgMap.put(namedAttribute,value);
771                 }
772                 // raw arguments
773                 arguments.add(value);
774             }
775 
776             Object nobj;
777             try
778             {
779                 if (namedArgMap.size() > 0)
780                 {
781                    LOG.debug("using named mapping");
782                    nobj = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
783                 }
784                 else
785                 {
786                     LOG.debug("using normal mapping");
787                     nobj = TypeUtil.construct(oClass, arguments.toArray());
788                 }
789             }
790             catch (NoSuchMethodException e)
791             {
792                 throw new IllegalStateException("No suitable constructor: " + node + " on " + obj);
793             }
794 
795             if (id != null)
796                 _configuration.getIdMap().put(id, nobj);
797             
798             _configuration.initializeDefaults(nobj);
799             configure(nobj,node,aoeNode.getNext());
800             return nobj;
801         }
802 
803         /*
804          * Reference an id value object.
805          *
806          * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException
807          */
808         private Object refObj(Object obj, XmlParser.Node node) throws Exception
809         {
810             String refid = node.getAttribute("refid");
811             if (refid==null)
812                 refid = node.getAttribute("id");
813             obj = _configuration.getIdMap().get(refid);
814             if (obj == null && node.size()>0)
815                 throw new IllegalStateException("No object for refid=" + refid);
816             configure(obj,node,0);
817             return obj;
818         }
819 
820         /*
821          * Create a new array object.
822          */
823         private Object newArray(Object obj, XmlParser.Node node) throws Exception
824         {
825             AttrOrElementNode aoeNode=new AttrOrElementNode(obj,node,"Id","Type","Item");
826             String id = aoeNode.getString("Id");
827             String type = aoeNode.getString("Type");
828             List<XmlParser.Node> items = aoeNode.getNodes("Item");
829             
830             // Get the type
831             Class<?> aClass = java.lang.Object.class;
832             if (type != null)
833             {
834                 aClass = TypeUtil.fromName(type);
835                 if (aClass == null)
836                 {
837                     switch (type)
838                     {
839                         case "String":
840                             aClass = String.class;
841                             break;
842                         case "URL":
843                             aClass = URL.class;
844                             break;
845                         case "InetAddress":
846                             aClass = InetAddress.class;
847                             break;
848                         default:
849                             aClass = Loader.loadClass(XmlConfiguration.class, type);
850                             break;
851                     }
852                 }
853             }
854             
855             Object al = null;
856 
857             for (XmlParser.Node item : items)
858             {
859                 String nid = item.getAttribute("id");
860                 Object v = value(obj,item);
861                 al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
862                 if (nid != null)
863                     _configuration.getIdMap().put(nid,v);
864             }
865 
866             Object array = LazyList.toArray(al,aClass);
867             if (id != null)
868                 _configuration.getIdMap().put(id,array);
869             return array;
870         }
871 
872         /*
873          * Create a new map object.
874          */
875         private Object newMap(Object obj, XmlParser.Node node) throws Exception
876         {
877             AttrOrElementNode aoeNode=new AttrOrElementNode(node,"Id","Entry");
878             String id = aoeNode.getString("Id");
879             List<XmlParser.Node> entries = aoeNode.getNodes("Entry");
880 
881             Map<Object, Object> map = new HashMap<>();
882             if (id != null)
883                 _configuration.getIdMap().put(id, map);
884 
885             for (XmlParser.Node entry : entries)
886             {
887                 if (!entry.getTag().equals("Entry"))
888                     throw new IllegalStateException("Not an Entry");
889 
890                 XmlParser.Node key = null;
891                 XmlParser.Node value = null;
892 
893                 for (Object object : entry)
894                 {
895                     if (object instanceof String)
896                         continue;
897                     XmlParser.Node item = (XmlParser.Node)object;
898                     if (!item.getTag().equals("Item"))
899                         throw new IllegalStateException("Not an Item");
900                     if (key == null)
901                         key = item;
902                     else
903                         value = item;
904                 }
905 
906                 if (key == null || value == null)
907                     throw new IllegalStateException("Missing Item in Entry");
908                 String kid = key.getAttribute("id");
909                 String vid = value.getAttribute("id");
910 
911                 Object k = value(obj,key);
912                 Object v = value(obj,value);
913                 map.put(k,v);
914 
915                 if (kid != null)
916                     _configuration.getIdMap().put(kid,k);
917                 if (vid != null)
918                     _configuration.getIdMap().put(vid,v);
919             }
920 
921             return map;
922         }
923 
924         /*
925          * Get a Property.
926          *
927          * @param node
928          * @return
929          * @exception Exception
930          */
931         private Object propertyObj(XmlParser.Node node) throws Exception
932         {
933             AttrOrElementNode aoeNode=new AttrOrElementNode(node,"Id","Name","Deprecated","Default");
934             String id = aoeNode.getString("Id");
935             String name = aoeNode.getString("Name",true);
936             List<Object> deprecated = aoeNode.getList("Deprecated");
937             String dftValue = aoeNode.getString("Default");
938 
939             // Look for a value
940             Map<String,String> properties = _configuration.getProperties();
941             String value = properties.get(name);
942             
943             // Look for a deprecated name value
944 
945             String alternate=null;
946             if (!deprecated.isEmpty())
947             {
948                 for (Object d : deprecated)
949                 { 
950                     String v = properties.get(StringUtil.valueOf(d));
951                     if (v!=null)
952                     {
953                         if (value==null)
954                             LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
955                         else
956                             LOG.warn("Property '{}' is deprecated, value from '{}' used", d, name);
957                     }
958                     if (alternate==null)
959                         alternate=v;;
960                 }
961             }
962 
963             // use alternate from deprecated
964             if (value==null)
965                 value=alternate;
966             
967             // use default value
968             if (value==null)
969                 value=dftValue;
970 
971             // Set value if ID set
972             if (id != null)
973                 _configuration.getIdMap().put(id, value);
974             return value;
975         }
976 
977         /*
978          * Get a SystemProperty.
979          *
980          * @param node
981          * @return
982          * @exception Exception
983          */
984         private Object systemPropertyObj(XmlParser.Node node) throws Exception
985         {
986             AttrOrElementNode aoeNode=new AttrOrElementNode(node,"Id","Name","Deprecated","Default");
987             String id = aoeNode.getString("Id");
988             String name = aoeNode.getString("Name",true);
989             List<Object> deprecated = aoeNode.getList("Deprecated");
990             String dftValue = aoeNode.getString("Default");
991 
992             // Look for a value
993             String value = System.getProperty(name);
994             
995             // Look for a deprecated name value
996             String alternate=null;
997             if (!deprecated.isEmpty())
998             {
999                 for (Object d : deprecated)
1000                 { 
1001                     String v = System.getProperty(StringUtil.valueOf(d));
1002                     if (v!=null)
1003                     {
1004                         if (value==null)
1005                             LOG.warn("SystemProperty '{}' is deprecated, use '{}' instead", d, name);
1006                         else
1007                             LOG.warn("SystemProperty '{}' is deprecated, value from '{}' used", d, name);
1008                     }
1009                     if (alternate==null)
1010                         alternate=v;;
1011                 }
1012             }
1013 
1014             // use alternate from deprecated
1015             if (value==null)
1016                 value=alternate;
1017             
1018             // use default value
1019             if (value==null)
1020                 value=dftValue;
1021 
1022             // Set value if ID set
1023             if (id != null)
1024                 _configuration.getIdMap().put(id, value);
1025 
1026             return value;
1027         }
1028         
1029         /*
1030          * Get a Environment Property.
1031          *
1032          * @param node
1033          * @return
1034          * @exception Exception
1035          */
1036         private Object envObj(XmlParser.Node node) throws Exception
1037         {
1038             AttrOrElementNode aoeNode=new AttrOrElementNode(node,"Id","Name","Deprecated","Default");
1039             String id = aoeNode.getString("Id");
1040             String name = aoeNode.getString("Name",true);
1041             List<Object> deprecated = aoeNode.getList("Deprecated");
1042             String dftValue = aoeNode.getString("Default");
1043 
1044             // Look for a value
1045             String value = System.getenv(name);
1046             
1047             // Look for a deprecated name value
1048             if (value==null && !deprecated.isEmpty())
1049             {
1050                 for (Object d : deprecated)
1051                 {
1052                     value = System.getenv(StringUtil.valueOf(d));
1053                     if (value!=null)
1054                     {
1055                         LOG.warn("Property '{}' is deprecated, use '{}' instead", d, name);
1056                         break;
1057                     }
1058                 }
1059             }
1060             
1061             // use default value
1062             if (value==null)
1063                 value=dftValue;
1064 
1065             // Set value if ID set
1066             if (id != null)
1067                 _configuration.getIdMap().put(id, value);
1068 
1069             return value;
1070         }
1071 
1072         /*
1073          * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
1074          * are added as strings before being converted to any specified type. @param node
1075          */
1076         private Object value(Object obj, XmlParser.Node node) throws Exception
1077         {
1078             Object value;
1079 
1080             // Get the type
1081             String type = node.getAttribute("type");
1082 
1083             // Try a ref lookup
1084             String ref = node.getAttribute("ref");
1085             if (ref != null)
1086             {
1087                 value = _configuration.getIdMap().get(ref);
1088             }
1089             else
1090             {
1091                 // handle trivial case
1092                 if (node.size() == 0)
1093                 {
1094                     if ("String".equals(type))
1095                         return "";
1096                     return null;
1097                 }
1098 
1099                 // Trim values
1100                 int first = 0;
1101                 int last = node.size() - 1;
1102 
1103                 // Handle default trim type
1104                 if (type == null || !"String".equals(type))
1105                 {
1106                     // Skip leading white
1107                     Object item;
1108                     while (first <= last)
1109                     {
1110                         item = node.get(first);
1111                         if (!(item instanceof String))
1112                             break;
1113                         item = ((String)item).trim();
1114                         if (((String)item).length() > 0)
1115                             break;
1116                         first++;
1117                     }
1118 
1119                     // Skip trailing white
1120                     while (first < last)
1121                     {
1122                         item = node.get(last);
1123                         if (!(item instanceof String))
1124                             break;
1125                         item = ((String)item).trim();
1126                         if (((String)item).length() > 0)
1127                             break;
1128                         last--;
1129                     }
1130 
1131                     // All white, so return null
1132                     if (first > last)
1133                         return null;
1134                 }
1135 
1136                 if (first == last)
1137                     // Single Item value
1138                     value = itemValue(obj,node.get(first));
1139                 else
1140                 {
1141                     // Get the multiple items as a single string
1142                     StringBuilder buf = new StringBuilder();
1143                     for (int i = first; i <= last; i++)
1144                     {
1145                         Object item = node.get(i);
1146                         buf.append(itemValue(obj,item));
1147                     }
1148                     value = buf.toString();
1149                 }
1150             }
1151 
1152             // Untyped or unknown
1153             if (value == null)
1154             {
1155                 if ("String".equals(type))
1156                     return "";
1157                 return null;
1158             }
1159 
1160             // Try to type the object
1161             if (type == null)
1162             {
1163                 if (value instanceof String)
1164                     return ((String)value).trim();
1165                 return value;
1166             }
1167 
1168             if (isTypeMatchingClass(type,String.class))
1169                 return value.toString();
1170 
1171             Class<?> pClass = TypeUtil.fromName(type);
1172             if (pClass != null)
1173                 return TypeUtil.valueOf(pClass,value.toString());
1174 
1175             if (isTypeMatchingClass(type,URL.class))
1176             {
1177                 if (value instanceof URL)
1178                     return value;
1179                 try
1180                 {
1181                     return new URL(value.toString());
1182                 }
1183                 catch (MalformedURLException e)
1184                 {
1185                     throw new InvocationTargetException(e);
1186                 }
1187             }
1188 
1189             if (isTypeMatchingClass(type,InetAddress.class))
1190             {
1191                 if (value instanceof InetAddress)
1192                     return value;
1193                 try
1194                 {
1195                     return InetAddress.getByName(value.toString());
1196                 }
1197                 catch (UnknownHostException e)
1198                 {
1199                     throw new InvocationTargetException(e);
1200                 }
1201             }
1202 
1203             for (Class<?> collectionClass : __supportedCollections)
1204             {
1205                 if (isTypeMatchingClass(type,collectionClass))
1206                     return convertArrayToCollection(value,collectionClass);
1207             }
1208 
1209             throw new IllegalStateException("Unknown type " + type);
1210         }
1211 
1212         private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
1213         {
1214             return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
1215         }
1216 
1217         /*
1218          * Get the value of a single element. @param obj @param item @return @exception Exception
1219          */
1220         private Object itemValue(Object obj, Object item) throws Exception
1221         {
1222             // String value
1223             if (item instanceof String)
1224                 return item;
1225 
1226             XmlParser.Node node = (XmlParser.Node)item;
1227             String tag = node.getTag();
1228             if ("Call".equals(tag))
1229                 return call(obj,node);
1230             if ("Get".equals(tag))
1231                 return get(obj,node);
1232             if ("New".equals(tag))
1233                 return newObj(obj,node);
1234             if ("Ref".equals(tag))
1235                 return refObj(obj,node);
1236             if ("Array".equals(tag))
1237                 return newArray(obj,node);
1238             if ("Map".equals(tag))
1239                 return newMap(obj,node);
1240             if ("Property".equals(tag))
1241                 return propertyObj(node);
1242             if ("SystemProperty".equals(tag))
1243                 return systemPropertyObj(node);
1244             if ("Env".equals(tag))
1245                 return envObj(node);
1246 
1247             LOG.warn("Unknown value tag: " + node,new Throwable());
1248             return null;
1249         }
1250         
1251 
1252         private class AttrOrElementNode
1253         {
1254             final Object _obj;
1255             final XmlParser.Node _node;
1256             final Set<String> _elements = new HashSet<>();
1257             final int _next;
1258 
1259             AttrOrElementNode(XmlParser.Node node,String... elements )
1260             {
1261                 this(null,node,elements);
1262             }
1263             
1264             AttrOrElementNode(Object obj, XmlParser.Node node,String... elements )
1265             {
1266                 _obj=obj;
1267                 _node=node;
1268                 for (String e:elements)
1269                     _elements.add(e);
1270                 
1271                 int next=0;
1272                 for (Object o: _node)
1273                 {
1274                     if (o instanceof String)
1275                     {
1276                         if (((String)o).trim().length()==0)
1277                         {
1278                             next++;
1279                             continue;
1280                         }
1281                         break;
1282                     }
1283                     
1284                     if (!(o instanceof XmlParser.Node))
1285                         break;
1286                     
1287                     XmlParser.Node n = (XmlParser.Node)o;
1288                     if (!_elements.contains(n.getTag()))
1289                         break;
1290                     
1291                     next++;
1292                 }
1293                 _next=next;
1294             }
1295 
1296             public int getNext()
1297             {
1298                 return _next;
1299             }
1300 
1301             public String getString(String elementName) throws Exception
1302             {
1303                 return StringUtil.valueOf(get(elementName,false));
1304             }
1305             
1306             public String getString(String elementName, boolean manditory) throws Exception
1307             {
1308                 return StringUtil.valueOf(get(elementName,manditory));
1309             }
1310             
1311             public Object get(String elementName, boolean manditory) throws Exception
1312             {
1313                 String attrName=StringUtil.asciiToLowerCase(elementName);
1314                 String attr = _node.getAttribute(attrName);
1315                 Object value=attr;
1316                 
1317                 for (int i=0;i<_next;i++)
1318                 {
1319                     Object o = _node.get(i);
1320                     if (!(o instanceof XmlParser.Node))
1321                         continue;
1322                     XmlParser.Node n = (XmlParser.Node)o;
1323                     if (elementName.equals(n.getTag()))
1324                     {
1325                         if (attr!=null)
1326                             throw new IllegalStateException("Cannot have attr '"+attrName+"' and element '"+elementName+"'");
1327 
1328                         value=value(_obj,n);
1329                         break;
1330                     }
1331                 }
1332                 
1333                 if (manditory && value==null)
1334                     throw new IllegalStateException("Must have attr '"+attrName+"' or element '"+elementName+"'");
1335                 
1336                 return value;
1337             }
1338 
1339             public List<Object> getList(String elementName) throws Exception
1340             {
1341                 return getList(elementName,false);
1342             }
1343             
1344             public List<Object> getList(String elementName, boolean manditory) throws Exception
1345             {
1346                 String attrName=StringUtil.asciiToLowerCase(elementName);
1347                 final List<Object> values=new ArrayList<>();
1348                 
1349                 String attr = _node.getAttribute(attrName);
1350                 if (attr!=null)
1351                     values.addAll(StringUtil.csvSplit(null,attr,0,attr.length()));
1352 
1353 
1354                 for (int i=0;i<_next;i++)
1355                 {
1356                     Object o = _node.get(i);
1357                     if (!(o instanceof XmlParser.Node))
1358                         continue;
1359                     XmlParser.Node n = (XmlParser.Node)o;
1360                     
1361                     if (elementName.equals(n.getTag()))
1362                     {
1363                         if (attr!=null)
1364                             throw new IllegalStateException("Cannot have attr '"+attrName+"' and element '"+elementName+"'");
1365 
1366                         values.add(value(_obj,n));
1367                     }
1368                 }
1369                 
1370                 if (manditory && values.isEmpty())
1371                     throw new IllegalStateException("Must have attr '"+attrName+"' or element '"+elementName+"'");
1372 
1373                 return values;
1374             }
1375             
1376             public List<XmlParser.Node> getNodes(String elementName) throws Exception
1377             {
1378                 String attrName=StringUtil.asciiToLowerCase(elementName);
1379                 final List<XmlParser.Node> values=new ArrayList<>();
1380                 
1381                 String attr = _node.getAttribute(attrName);
1382                 if (attr!=null)
1383                 {
1384                     for (String a : StringUtil.csvSplit(null,attr,0,attr.length()))
1385                     {
1386                         // create a fake node
1387                         XmlParser.Node n = new XmlParser.Node(null,elementName,null);
1388                         n.add(a);
1389                         values.add(n);
1390                     }
1391                 }
1392 
1393                 for (int i=0;i<_next;i++)
1394                 {
1395                     Object o = _node.get(i);
1396                     if (!(o instanceof XmlParser.Node))
1397                         continue;
1398                     XmlParser.Node n = (XmlParser.Node)o;
1399                     
1400                     if (elementName.equals(n.getTag()))
1401                     {
1402                         if (attr!=null)
1403                             throw new IllegalStateException("Cannot have attr '"+attrName+"' and element '"+elementName+"'");
1404 
1405                         values.add(n);
1406                     }
1407                 }
1408 
1409                 return values;
1410             }
1411         }
1412     }
1413 
1414     /**
1415      * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
1416      * files.
1417      * <p>
1418      * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
1419      * {@link XmlConfiguration#getProperties()}.
1420      * <p>
1421      * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
1422      * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
1423      * <p>
1424      * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()}.
1425      * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
1426      *
1427      * @param args
1428      *            array of property and xml configuration filenames or {@link Resource}s.
1429      * @throws Exception if the XML configurations cannot be run
1430      */
1431     public static void main(final String... args) throws Exception
1432     {
1433         final AtomicReference<Throwable> exception = new AtomicReference<>();
1434 
1435         AccessController.doPrivileged(new PrivilegedAction<Object>()
1436         {
1437             public Object run()
1438             {
1439                 try
1440                 {
1441                     Properties properties = null;
1442 
1443                     // Look for properties from start.jar
1444                     try
1445                     {
1446                         Class<?> config = XmlConfiguration.class.getClassLoader().loadClass("org.eclipse.jetty.start.Config");
1447                         properties = (Properties)config.getMethod("getProperties").invoke(null);
1448                         LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
1449                     }
1450                     catch (NoClassDefFoundError | ClassNotFoundException e)
1451                     {
1452                         LOG.ignore(e);
1453                     }
1454                     catch (Exception e)
1455                     {
1456                         LOG.warn(e);
1457                     }
1458 
1459                     // If no start.config properties, use clean slate
1460                     if (properties == null)
1461                     {
1462                         // Add System Properties
1463                         properties = new Properties();
1464                         properties.putAll(System.getProperties());
1465                     }
1466 
1467                     // For all arguments, load properties
1468                     for (String arg : args)
1469                     {
1470                         if (arg.indexOf('=')>=0)
1471                         {
1472                             int i=arg.indexOf('=');
1473                             properties.put(arg.substring(0,i),arg.substring(i+1));
1474                         }
1475                         else if (arg.toLowerCase(Locale.ENGLISH).endsWith(".properties"))
1476                             properties.load(Resource.newResource(arg).getInputStream());
1477                     }
1478 
1479                     // For all arguments, parse XMLs
1480                     XmlConfiguration last = null;
1481                     Object[] obj = new Object[args.length];
1482                     for (int i = 0; i < args.length; i++)
1483                     {
1484                         if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties") && (args[i].indexOf('=')<0))
1485                         {
1486                             XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURI().toURL());
1487                             if (last != null)
1488                                 configuration.getIdMap().putAll(last.getIdMap());
1489                             if (properties.size() > 0)
1490                             {
1491                                 Map<String, String> props = new HashMap<>();
1492                                 for (Object key : properties.keySet())
1493                                 {
1494                                     props.put(key.toString(),String.valueOf(properties.get(key)));
1495                                 }
1496                                 configuration.getProperties().putAll(props);
1497                             }
1498                             obj[i] = configuration.configure();
1499                             last = configuration;
1500                         }
1501                     }
1502 
1503                     // For all objects created by XmlConfigurations, start them if they are lifecycles.
1504                     for (int i = 0; i < args.length; i++)
1505                     {
1506                         if (obj[i] instanceof LifeCycle)
1507                         {
1508                             LifeCycle lc = (LifeCycle)obj[i];
1509                             if (!lc.isRunning())
1510                                 lc.start();
1511                         }
1512                     }
1513                 }
1514                 catch (Exception e)
1515                 {
1516                     LOG.debug(Log.EXCEPTION,e);
1517                     exception.set(e);
1518                 }
1519                 return null;
1520             }
1521         });
1522 
1523         Throwable th = exception.get();
1524         if (th != null)
1525         {
1526             if (th instanceof RuntimeException)
1527                 throw (RuntimeException)th;
1528             else if (th instanceof Exception)
1529                 throw (Exception)th;
1530             else if (th instanceof Error)
1531                 throw (Error)th;
1532             throw new Error(th);
1533         }
1534     }
1535 }