clean out deadwood: remove unsused source files
[mir.git] / source / mir / xml / XmlConfigurator.java
1 /*
2  * Copyright (C) 2001, 2002  The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with the com.oreilly.servlet library, any library
22  * licensed under the Apache Software License, The Sun (tm) Java Advanced
23  * Imaging library (JAI), The Sun JIMI library (or with modified versions of
24  * the above that use the same license as the above), and distribute linked
25  * combinations including the two.  You must obey the GNU General Public
26  * License in all respects for all of the code used other than the above
27  * mentioned libraries.  If you modify this file, you may extend this exception
28  * to your version of the file, but you are not obligated to do so.  If you do
29  * not wish to do so, delete this exception statement from your version.
30  */
31
32 package mir.xml;
33
34 import java.io.*;
35 import java.util.*;
36 import java.lang.reflect.*;
37 import org.xml.sax.helpers.DefaultHandler;
38 import org.xml.sax.*;
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.parsers.SAXParser;
41 import javax.xml.parsers.SAXParserFactory;
42
43
44 import mir.misc.ConfigException;
45 import mir.misc.Location;
46
47 /**
48  * Configures a based on
49  * a XML config file.
50  *
51  * Based and inspired by ant's XmlConfigurator.java
52  * It's a simplified version of the ant parser with
53  * the addition of calling set* methods for defined
54  * classes as well as the inclusion of a method to
55  * add parameters (nested tags) that are required.
56  * (the addRequired method) in the config file.
57  * that part is from tomcat.
58  *
59  * much code is stolen from ant ProjectHelper.java.
60  *
61  * @author -mh <heckmann@hbe.ca>
62  * @version 2001.10.21
63  */
64
65 public class XmlConfigurator {
66
67     private static SAXParserFactory parserFactory = null;
68
69     private SAXParser saxParser;
70     //private Project project;
71     private File configFile;
72     private File configFileParent;
73     private Locator locator;
74
75     private SaxContext saxContext;
76
77     XmlMatch requiredXmlMatch[]=new XmlMatch[256]; //maximum amount of rules
78     int requiredXmlMatchCount=0;
79     boolean matched[] = new boolean[256];
80     int matchedCount=0;
81
82     XmlMatch mustComeFirstMatch[]=new XmlMatch[256]; //maximum amount of rules
83     int comeFirstMatchCount=0;
84
85     Property comesFirstArr[]=new Property[128];
86     int comesFirstCount=0;
87
88     Property propertyArr[]=new Property[128];
89     int propertyCount=0;
90
91     private static XmlConfigurator instance = new XmlConfigurator();
92     public static XmlConfigurator getInstance() { return instance; }
93
94     /**
95      * Configures the Project with the contents of the specified XML file.
96      */
97     public void configure(File configFile) throws ConfigException {
98         setConfigFile(configFile);
99         parse();
100     }
101
102     /**
103      * konstruktor. private so no one calls "new" on us.
104      */
105     private XmlConfigurator() {}
106
107
108     /**
109      * Constructs a new Ant parser for the specified XML file.
110      */
111     private void setConfigFile(File configFile) {
112         this.configFile = new File(configFile.getAbsolutePath());
113         configFileParent = new File(this.configFile.getParent());
114         saxContext = new SaxContext();
115     }
116
117     /**
118      * Parses the config file.
119      */
120     private void parse() throws ConfigException {
121         FileInputStream inputStream = null;
122         InputSource inputSource = null;
123         
124         try {
125             SAXParserFactory factory = SAXParserFactory.newInstance();
126             saxParser = factory.newSAXParser();
127                 
128             String uri = "file:" + configFile.getAbsolutePath().replace('\\', '/');
129             for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) {
130                 uri = uri.substring(0, index) + "%23" + uri.substring(index+1);
131             }
132             
133             inputStream = new FileInputStream(configFile);
134             inputSource = new InputSource(inputStream);
135             inputSource.setSystemId(uri);
136             saxParser.parse(inputSource, new RootHandler());
137             if(matchedCount < requiredXmlMatchCount) {
138                 for( int i=0; i<requiredXmlMatchCount; i++) {
139                     if( !matched[i] )
140                         throw new ConfigException("Error parsing config file, missing required element: "+requiredXmlMatch[i].toString());
141                 }
142             }
143             try {
144                 for(int i=0; i<comesFirstCount;i++) {
145                     comesFirstArr[i].set();
146                 }
147                 for(int i=0; i<propertyCount;i++) {
148                     propertyArr[i].set();
149                     System.out.println("about to set: "+i);
150                 }
151             } catch (Exception e) {
152                 throw new SAXParseException(e.toString(), locator);
153             }
154 }
155         catch(ParserConfigurationException exc) {
156             throw new ConfigException("Parser has not been configured correctly", exc);
157         }
158         catch(SAXParseException exc) {
159             Location location =
160                 new Location(configFile.toString(), exc.getLineNumber(), exc.getColumnNumber());
161
162             Throwable t = exc.getException();
163             if (t instanceof ConfigException) {
164                 ConfigException be = (ConfigException) t;
165                 if (be.getLocation() == Location.UNKNOWN_LOCATION) {
166                     be.setLocation(location);
167                 }
168                 throw be;
169             }
170             
171             throw new ConfigException(exc.getMessage(), t, location);
172         }
173         catch(SAXException exc) {
174             Throwable t = exc.getException();
175             if (t instanceof ConfigException) {
176                 throw (ConfigException) t;
177             }
178             throw new ConfigException(exc.getMessage(), t);
179         }
180         catch(FileNotFoundException exc) {
181             throw new ConfigException(exc);
182         }
183         catch(IOException exc) {
184             throw new ConfigException("Error reading config file", exc);
185         }
186         finally {
187             if (inputStream != null) {
188                 try {
189                     inputStream.close();
190                 }
191                 catch (IOException ioe) {
192                     // ignore this
193                 }
194             }
195         }
196     }
197
198     /**
199      * The common superclass for all sax event handlers in Ant. Basically
200      * throws an exception in each method, so subclasses should override
201      * what they can handle.
202      *
203      * Each type of xml element (task, target, etc) in ant will
204      * have its own subclass of AbstractHandler.
205      *
206      * In the constructor, this class    takes over the handling of sax
207      * events from the parent handler, and returns
208      * control back to the parent in the endElement method.
209      */
210     private class AbstractHandler extends DefaultHandler {
211         protected ContentHandler parentHandler;
212
213         public AbstractHandler(ContentHandler parentHandler) {
214             this.parentHandler = parentHandler;
215
216             // Start handling SAX events
217             try {
218                 saxParser.getXMLReader().setContentHandler(this);
219             } catch (SAXException e) {
220                 throw new ConfigException("Error getting XMLReader",e);
221             }
222                 
223         }
224
225         public void startElement(String uri, String tag, String qName, Attributes attrs) throws SAXParseException {
226             throw new SAXParseException("Unexpected element \"" + tag + "\"", locator);
227         }
228
229         public void characters(char[] buf, int start, int end) throws SAXParseException {
230             String s = new String(buf, start, end).trim();
231
232             if (s.length() > 0) {
233                 throw new SAXParseException("Unexpected text \"" + s + "\"", locator);
234             }
235         }
236
237         /**
238          * Called when this element and all elements nested into it have been
239          * handeled.
240          */
241         protected void finished() {}
242
243         public void endElement(String uri, String tag, String qName) throws SAXException {
244             finished();
245             // Let parent resume handling SAX events
246             saxParser.getXMLReader().setContentHandler(parentHandler);
247         }
248     }
249
250     /**
251      * Handler for the root element. It's only child must be the "mir" element.
252      */
253     private class RootHandler extends DefaultHandler {
254
255         /**
256          * resolve file: URIs as relative to the build file.
257          */
258         public InputSource resolveEntity(String publicId,
259                                          String systemId) {
260         
261         
262             if (systemId.startsWith("file:")) {
263                 String path = systemId.substring(5);
264                 int index = path.indexOf("file:");
265                 
266                 // we only have to handle these for backward compatibility
267                 // since they are in the FAQ.
268                 while (index != -1) {
269                     path = path.substring(0, index) + path.substring(index + 5);
270                     index = path.indexOf("file:");
271                 }
272                 
273                 String entitySystemId = path;
274                 index = path.indexOf("%23");
275                 // convert these to #
276                 while (index != -1) {
277                     path = path.substring(0, index) + "#" + path.substring(index + 3);
278                     index = path.indexOf("%23");
279                 }
280
281                 File file = new File(path);
282                 if (!file.isAbsolute()) {
283                     file = new File(configFileParent, path);
284                 }
285                 
286                 try {
287                     InputSource inputSource = new InputSource(new FileInputStream(file));
288                     inputSource.setSystemId("file:" + entitySystemId);
289                     return inputSource;
290                 } catch (FileNotFoundException fne) {
291                     System.out.println(file.getAbsolutePath()+" could not be found");
292                 }
293             }
294             // use default if not file or file not found
295             return null;
296         }
297
298         public void startElement(String uri, String tag, String qName, Attributes attrs) throws SAXParseException {
299             if (tag.equals("mir")) {
300                 new MirHandler(this).init(tag, attrs);
301             } else {
302                 throw new SAXParseException("Config file is not of expected XML type", locator);
303             }
304         }
305
306         public void setDocumentLocator(Locator locator) {
307             XmlConfigurator.this.locator = locator;
308         }
309     }
310
311     /**
312      * Handler for the top level "project" element.
313      */
314     private class MirHandler extends AbstractHandler {
315         public MirHandler(ContentHandler parentHandler) {
316             super(parentHandler);
317         }
318
319         public void init(String tag, Attributes attrs) throws SAXParseException {
320             String name = null;
321
322             for (int i = 0; i < attrs.getLength(); i++) {
323                 String key = attrs.getLocalName(i);
324                 String value = attrs.getValue(i);
325
326                 if (key.equals("name")) {
327                     name = value;
328                 } else {
329                     throw new SAXParseException("Unexpected attribute \"" + attrs.getLocalName(i) + "\"", locator);
330                 }
331             }
332
333             if (name == null) {
334                 throw new SAXParseException("The default attribute of \"name\" is required", 
335                                             locator);
336             }
337             
338             saxContext.push(tag);
339             matchedCount += checkRequiredTag(saxContext);
340
341             //MirConfig.setName(name);
342
343         }
344
345         public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
346             if (name.equals("class")) {
347                 handleClassdef(name, attrs);
348             } else {
349                 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
350             }
351         }
352
353         public void finished() {
354             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
355             saxContext.pop();
356         }
357
358         private void handleClassdef(String name, Attributes attrs) throws SAXParseException {
359             (new ClassHandler(this)).init(name, attrs);
360         }
361
362     }
363
364     /**
365      * Handler for "class" elements.
366      */
367     private class ClassHandler extends AbstractHandler {
368
369         Class classN;
370
371         public ClassHandler(ContentHandler parentHandler) {
372             super(parentHandler);
373         }
374
375         public void init(String tag, Attributes attrs) throws SAXParseException {
376             String name = null;
377
378             for (int i = 0; i < attrs.getLength(); i++) {
379                 String key = attrs.getLocalName(i);
380                 String value = attrs.getValue(i);
381
382                 if (key.equals("name")) {
383                     name = value;
384                 } else {
385                     throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
386                 }
387             }
388
389             if (name == null) {
390                 throw new SAXParseException("class element appears without a \"name\" attribute", locator);
391             }
392
393             saxContext.push(tag+":"+name);
394             matchedCount += checkRequiredTag(saxContext);
395             try {
396                 classN=Class.forName(name, false, this.getClass().getClassLoader());
397             } catch (ClassNotFoundException e) {
398                 throw new ConfigException("Error invoking class: \""+name+
399                     "\"",e);
400             }
401
402         }
403
404         public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
405             if (name.equals("property")) {
406                 handleProperties(name, attrs);
407             } else {
408                 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
409             }
410         }
411
412         public void finished() {
413             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
414             System.out.println("COUNT "+saxContext.getTagCount());
415             saxContext.pop();
416         }
417
418         private void handleProperties(String name, Attributes attrs) throws SAXParseException {
419             (new PropertiesHandler(this, classN )).init(name, attrs);
420         }
421
422     }
423
424     /**
425      * Handler for all property elements.
426      */
427     private class PropertiesHandler extends AbstractHandler {
428         private Class classN;
429
430         public PropertiesHandler(ContentHandler parentHandler, Class classN) {
431             super(parentHandler);
432
433             this.classN = classN;
434         }
435
436         public void init(String tag, Attributes attrs) throws SAXParseException {
437             String name=null;
438             String value=null;
439
440             for (int i = 0; i < attrs.getLength(); i++) {
441                 String key = attrs.getLocalName(i);
442                 String v = attrs.getValue(i);
443
444                 if (key.equals("name")) {
445                     name = v;
446                 } else if (key.equals("value")) {
447                     value = v; 
448                 } else {
449                     throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
450                 }
451             }
452
453             if (name == null) {
454                 throw new SAXParseException("property element appears without a \"name\" attribute", locator);
455             }
456             if (value == null) {
457                 throw new SAXParseException("property element appears without a \"value\" attribute", locator);
458             }
459             saxContext.push(tag+":"+name);
460             matchedCount += checkRequiredTag(saxContext);
461
462             //finally add it to the lists
463             //to be processed later
464             if (checkComesFirstTag(saxContext)) {
465                 comesFirstArr[comesFirstCount]=new Property(classN, name, value);
466                 comesFirstCount++;
467             } else {
468                 propertyArr[propertyCount]=new Property(classN, name, value);
469                 propertyCount++;
470             }
471         }
472
473         protected void finished() {
474             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
475             System.out.println("COUNT "+saxContext.getTagCount());
476             saxContext.pop();
477         }
478
479     }
480
481     public void addComesFirstTag(String xmlPath) {
482         mustComeFirstMatch[comeFirstMatchCount]=new XmlMatch(xmlPath);
483         comeFirstMatchCount++;
484     }
485
486     private boolean checkComesFirstTag(SaxContext ctx) {
487         for( int i=0; i<comeFirstMatchCount; i++ ) {
488             if( mustComeFirstMatch[i].match(ctx) ) {
489                 return true;
490             }
491         }
492         return false;
493     }
494
495     public void addRequiredTag(String xmlPath) {
496         requiredXmlMatch[requiredXmlMatchCount]=new XmlMatch(xmlPath);
497         matched[requiredXmlMatchCount]=false;
498         requiredXmlMatchCount++;
499     }
500
501     private int checkRequiredTag(SaxContext ctx) {
502         int matchCount=0;
503         for( int i=0; i<requiredXmlMatchCount; i++ ) {
504             if( requiredXmlMatch[i].match(ctx) ) {
505                 matched[i]=true;
506                 matchCount++;
507             }
508         }
509         return matchCount;
510     }
511
512
513     private static String capitalize(String name) {
514         if (name == null || name.length() == 0) {
515             return name;
516         }
517         char chars[] = name.toCharArray();
518         chars[0] = Character.toUpperCase(chars[0]);
519         return new String(chars);
520     }
521
522     private class Property {
523         Class classN;
524         String name;
525         String value;
526
527         public Property( Class classN, String name, String value) {
528             this.classN=classN;
529             this.name=name;
530             this.value=value;
531         }
532             
533         /** Find a method with the right name
534          * If found, call the method ( if param is int or boolean we'll convert 
535          * value to the right type before) - that means you can have setDebug(1).
536          */
537         public void set() throws Exception {
538             
539             String setter= "set" +capitalize(name);
540
541             try {
542                 Method methods[]=classN.getMethods();
543                 Method setPropertyMethod=null;
544
545                 // First, the ideal case - a setFoo( String ) method
546                 for( int i=0; i< methods.length; i++ ) {
547                     Class paramT[]=methods[i].getParameterTypes();
548                     if( setter.equals( methods[i].getName() ) &&
549                         paramT.length == 1 &&
550                         "java.lang.String".equals( paramT[0].getName())) {
551
552                         methods[i].invoke( null, new Object[] { value } );
553                         return;
554                     }
555                 } //end for
556
557                 // Try a setFoo ( int ), (float) or ( boolean )
558                 for( int i=0; i< methods.length; i++ ) {
559                     boolean ok=true;
560                     if( setter.equals( methods[i].getName() ) &&
561                         methods[i].getParameterTypes().length == 1) {
562
563                         // match - find the type and invoke it
564                         Class paramType=methods[i].getParameterTypes()[0];
565                         Object params[]=new Object[1];
566                         if ("java.lang.Integer".equals( paramType.getName()) ||
567                             "int".equals( paramType.getName())) {
568                             try {
569                                 params[0]=new Integer(value);
570                             } catch( NumberFormatException ex ) {ok=false;}
571                         } else if ("java.lang.Float".equals( paramType.getName()) ||
572                             "float".equals( paramType.getName())) {
573                             try {
574                                 params[0]=new Float(value);
575                             } catch( NumberFormatException ex ) {ok=false;}
576                         } else if ("java.lang.Boolean".equals( paramType.getName()) ||
577                             "boolean".equals( paramType.getName())) {
578                             params[0]=new Boolean(value);
579                         } else {
580                             throw new Exception("Unknown type " + paramType.getName() + "for property \""+name+"\"with value \""+value+"\"");
581                         }
582
583                         if( ok ) {
584                             System.out.println("XXX: " + methods[i] + " " + classN + " " + params[0] );
585                             methods[i].invoke( null, params );
586                             return; 
587                         } //end if
588                     } //end if setter
589                 } //end for
590
591                 //if we got this far it means we were not successful in setting the
592                 //property
593                 throw new Exception("Count not find method \""+setter+"\" in Class \""+classN.getName()+"\" in order to set property \""+name+"\"");
594
595             } catch( SecurityException ex1 ) {
596                 throw new Exception("SecurityException for " + classN.getName() + " " +  name + "="  + value  +")" );
597                 //if( ctx.getDebug() > 1 ) ex1.printStackTrace();
598             } catch (IllegalAccessException iae) {
599                 throw new Exception("IllegalAccessException for " + classN.getName() + " " +  name + "="  + value  +")" );
600                 //if( ctx.getDebug() > 1 ) iae.printStackTrace();
601             } catch (InvocationTargetException ie) {
602                 throw new Exception("InvocationTargetException for " + classN.getName() + " " +  name + "="  + value  +")" );
603                 //if( ctx.getDebug() > 1 ) ie.printStackTrace();
604             }
605         }
606     }
607
608 }