c82f22319c2f497d9c34a4d85c1ce1e3df6a58ef
[mir.git] / source / mir / xml / XmlConfigurator.java
1 package mir.xml;
2
3 import java.io.*;
4 import java.util.*;
5 import org.xml.sax.helpers.DefaultHandler;
6 import org.xml.sax.*;
7 import javax.xml.parsers.ParserConfigurationException;
8 import javax.xml.parsers.SAXParser;
9 import javax.xml.parsers.SAXParserFactory;
10
11
12 import mir.misc.ConfigException;
13 import mir.misc.Location;
14
15 /**
16  * Configures a based on
17  * a XML config file.
18  *
19  * Based and inspired by ant's XmlConfigurator.java
20  * It's a simplified version of the ant parser with
21  * the addition of calling set* methods for defined
22  * classes as well as the inclusion of a method to
23  * add parameters (nested tags) that are required.
24  * (the addRequired method) in the config file.
25  *
26  * @author -mh <heckmann@hbe.ca>
27  * @version 2001.10.21
28  */
29
30 public class XmlConfigurator {
31
32     private static SAXParserFactory parserFactory = null;
33
34     private SAXParser saxParser;
35     //private Project project;
36     private File configFile;
37     private File configFileParent;
38     private Locator locator;
39
40     private SaxContext saxContext;
41
42     XmlMatch requiredXmlMatch[]=new XmlMatch[256]; //maximum amount of rules
43     int requiredXmlMatchCount=0;
44     boolean matched[] = new boolean[256];
45     int matchedCount=0;
46
47     private static XmlConfigurator instance = new XmlConfigurator();
48     public static XmlConfigurator getInstance() { return instance; }
49
50     /**
51      * Configures the Project with the contents of the specified XML file.
52      */
53     public void configure(File configFile) throws ConfigException {
54         setConfigFile(configFile);
55         parse();
56     }
57
58     /**
59      * konstruktor. private so no one calls "new" on us.
60      */
61     private XmlConfigurator() {}
62
63
64     /**
65      * Constructs a new Ant parser for the specified XML file.
66      */
67     private void setConfigFile(File configFile) {
68         this.configFile = new File(configFile.getAbsolutePath());
69         configFileParent = new File(this.configFile.getParent());
70         saxContext = new SaxContext();
71     }
72
73     /**
74      * Parses the config file.
75      */
76     private void parse() throws ConfigException {
77         FileInputStream inputStream = null;
78         InputSource inputSource = null;
79         
80         try {
81             SAXParserFactory factory = SAXParserFactory.newInstance();
82             saxParser = factory.newSAXParser();
83                 
84             String uri = "file:" + configFile.getAbsolutePath().replace('\\', '/');
85             for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) {
86                 uri = uri.substring(0, index) + "%23" + uri.substring(index+1);
87             }
88             
89             inputStream = new FileInputStream(configFile);
90             inputSource = new InputSource(inputStream);
91             inputSource.setSystemId(uri);
92             saxParser.parse(inputSource, new RootHandler());
93             if(matchedCount < requiredXmlMatchCount) {
94                 for( int i=0; i<requiredXmlMatchCount; i++) {
95                     if( !matched[i] )
96                         throw new ConfigException("Error parsing config file, missing required element: "+requiredXmlMatch[i].toString());
97                 }
98             }
99         }
100         catch(ParserConfigurationException exc) {
101             throw new ConfigException("Parser has not been configured correctly", exc);
102         }
103         catch(SAXParseException exc) {
104             Location location =
105                 new Location(configFile.toString(), exc.getLineNumber(), exc.getColumnNumber());
106
107             Throwable t = exc.getException();
108             if (t instanceof ConfigException) {
109                 ConfigException be = (ConfigException) t;
110                 if (be.getLocation() == Location.UNKNOWN_LOCATION) {
111                     be.setLocation(location);
112                 }
113                 throw be;
114             }
115             
116             throw new ConfigException(exc.getMessage(), t, location);
117         }
118         catch(SAXException exc) {
119             Throwable t = exc.getException();
120             if (t instanceof ConfigException) {
121                 throw (ConfigException) t;
122             }
123             throw new ConfigException(exc.getMessage(), t);
124         }
125         catch(FileNotFoundException exc) {
126             throw new ConfigException(exc);
127         }
128         catch(IOException exc) {
129             throw new ConfigException("Error reading config file", exc);
130         }
131         finally {
132             if (inputStream != null) {
133                 try {
134                     inputStream.close();
135                 }
136                 catch (IOException ioe) {
137                     // ignore this
138                 }
139             }
140         }
141     }
142
143     /**
144      * The common superclass for all sax event handlers in Ant. Basically
145      * throws an exception in each method, so subclasses should override
146      * what they can handle.
147      *
148      * Each type of xml element (task, target, etc) in ant will
149      * have its own subclass of AbstractHandler.
150      *
151      * In the constructor, this class    takes over the handling of sax
152      * events from the parent handler, and returns
153      * control back to the parent in the endElement method.
154      */
155     private class AbstractHandler extends DefaultHandler {
156         protected ContentHandler parentHandler;
157
158         public AbstractHandler(ContentHandler parentHandler) {
159             this.parentHandler = parentHandler;
160
161             // Start handling SAX events
162             try {
163                 saxParser.getXMLReader().setContentHandler(this);
164             } catch (SAXException e) {
165                 throw new ConfigException("Error getting XMLReader",e);
166             }
167                 
168         }
169
170         public void startElement(String uri, String tag, String qName, Attributes attrs) throws SAXParseException {
171             throw new SAXParseException("Unexpected element \"" + tag + "\"", locator);
172         }
173
174         public void characters(char[] buf, int start, int end) throws SAXParseException {
175             String s = new String(buf, start, end).trim();
176
177             if (s.length() > 0) {
178                 throw new SAXParseException("Unexpected text \"" + s + "\"", locator);
179             }
180         }
181
182         /**
183          * Called when this element and all elements nested into it have been
184          * handeled.
185          */
186         protected void finished() {}
187
188         public void endElement(String uri, String tag, String qName) throws SAXException {
189             finished();
190             // Let parent resume handling SAX events
191             saxParser.getXMLReader().setContentHandler(parentHandler);
192         }
193     }
194
195     /**
196      * Handler for the root element. It's only child must be the "mir" element.
197      */
198     private class RootHandler extends DefaultHandler {
199
200         /**
201          * resolve file: URIs as relative to the build file.
202          */
203         public InputSource resolveEntity(String publicId,
204                                          String systemId) {
205         
206         
207             if (systemId.startsWith("file:")) {
208                 String path = systemId.substring(5);
209                 int index = path.indexOf("file:");
210                 
211                 // we only have to handle these for backward compatibility
212                 // since they are in the FAQ.
213                 while (index != -1) {
214                     path = path.substring(0, index) + path.substring(index + 5);
215                     index = path.indexOf("file:");
216                 }
217                 
218                 String entitySystemId = path;
219                 index = path.indexOf("%23");
220                 // convert these to #
221                 while (index != -1) {
222                     path = path.substring(0, index) + "#" + path.substring(index + 3);
223                     index = path.indexOf("%23");
224                 }
225
226                 File file = new File(path);
227                 if (!file.isAbsolute()) {
228                     file = new File(configFileParent, path);
229                 }
230                 
231                 try {
232                     InputSource inputSource = new InputSource(new FileInputStream(file));
233                     inputSource.setSystemId("file:" + entitySystemId);
234                     return inputSource;
235                 } catch (FileNotFoundException fne) {
236                     System.out.println(file.getAbsolutePath()+" could not be found");
237                 }
238             }
239             // use default if not file or file not found
240             return null;
241         }
242
243         public void startElement(String uri, String tag, String qName, Attributes attrs) throws SAXParseException {
244             if (tag.equals("mir")) {
245                 new MirHandler(this).init(tag, attrs);
246             } else {
247                 throw new SAXParseException("Config file is not of expected XML type", locator);
248             }
249         }
250
251         public void setDocumentLocator(Locator locator) {
252             XmlConfigurator.this.locator = locator;
253         }
254     }
255
256     /**
257      * Handler for the top level "project" element.
258      */
259     private class MirHandler extends AbstractHandler {
260         public MirHandler(ContentHandler parentHandler) {
261             super(parentHandler);
262         }
263
264         public void init(String tag, Attributes attrs) throws SAXParseException {
265             String name = null;
266
267             for (int i = 0; i < attrs.getLength(); i++) {
268                 String key = attrs.getLocalName(i);
269                 String value = attrs.getValue(i);
270
271                 if (key.equals("name")) {
272                     name = value;
273                 } else {
274                     throw new SAXParseException("Unexpected attribute \"" + attrs.getLocalName(i) + "\"", locator);
275                 }
276             }
277
278             if (name == null) {
279                 throw new SAXParseException("The default attribute of \"name\" is required", 
280                                             locator);
281             }
282             
283             saxContext.push(tag);
284             matchedCount += checkRequiredTag(saxContext);
285
286             //MirConfig.setName(name);
287
288         }
289
290         public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
291             if (name.equals("class")) {
292                 handleClassdef(name, attrs);
293             } else {
294                 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
295             }
296         }
297
298         public void finished() {
299             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
300             saxContext.pop();
301         }
302
303         private void handleClassdef(String name, Attributes attrs) throws SAXParseException {
304             (new ClassHandler(this)).init(name, attrs);
305         }
306
307     }
308
309     /**
310      * Handler for "class" elements.
311      */
312     private class ClassHandler extends AbstractHandler {
313
314         Class classN;
315
316         public ClassHandler(ContentHandler parentHandler) {
317             super(parentHandler);
318         }
319
320         public void init(String tag, Attributes attrs) throws SAXParseException {
321             String name = null;
322
323             for (int i = 0; i < attrs.getLength(); i++) {
324                 String key = attrs.getLocalName(i);
325                 String value = attrs.getValue(i);
326
327                 if (key.equals("name")) {
328                     name = value;
329                 } else {
330                     throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
331                 }
332             }
333
334             if (name == null) {
335                 throw new SAXParseException("class element appears without a \"name\" attribute", locator);
336             }
337
338             saxContext.push(tag+":"+name);
339             matchedCount += checkRequiredTag(saxContext);
340             try {
341                 classN=Class.forName(name);
342             } catch (ClassNotFoundException e) {
343                 throw new ConfigException("Error invoking class: \""+name+
344                     "\"",e);
345             }
346
347         }
348
349         public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
350             if (name.equals("property")) {
351                 handleProperties(name, attrs);
352             } else {
353                 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
354             }
355         }
356
357         public void finished() {
358             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
359             System.out.println("COUNT "+saxContext.getTagCount());
360             saxContext.pop();
361         }
362
363         private void handleProperties(String name, Attributes attrs) throws SAXParseException {
364             (new PropertiesHandler(this, classN )).init(name, attrs);
365         }
366
367     }
368
369     /**
370      * Handler for all property elements.
371      */
372     private class PropertiesHandler extends AbstractHandler {
373         private Class classN;
374
375         public PropertiesHandler(ContentHandler parentHandler, Class classN) {
376             super(parentHandler);
377
378             this.classN = classN;
379         }
380
381         public void init(String tag, Attributes attrs) throws SAXParseException {
382             String name=null;
383             String value=null;
384
385             for (int i = 0; i < attrs.getLength(); i++) {
386                 String key = attrs.getLocalName(i);
387                 String v = attrs.getValue(i);
388
389                 if (key.equals("name")) {
390                     name = v;
391                 } else if (key.equals("value")) {
392                     value = v; 
393                 } else {
394                     throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
395                 }
396             }
397
398             if (name == null) {
399                 throw new SAXParseException("property element appears without a \"name\" attribute", locator);
400             }
401             if (value == null) {
402                 throw new SAXParseException("property element appears without a \"value\" attribute", locator);
403             }
404             saxContext.push(tag+":"+name);
405             matchedCount += checkRequiredTag(saxContext);
406             /////
407         }
408
409         protected void finished() {
410             System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
411             System.out.println("COUNT "+saxContext.getTagCount());
412             saxContext.pop();
413         }
414
415     }
416
417     public void addRequiredTag(String xmlPath) {
418         requiredXmlMatch[requiredXmlMatchCount]=new XmlMatch(xmlPath);
419         matched[requiredXmlMatchCount]=false;
420         requiredXmlMatchCount++;
421     }
422
423     private int checkRequiredTag(SaxContext ctx) {
424         int matchCount=0;
425         for( int i=0; i<requiredXmlMatchCount; i++ ) {
426             if( requiredXmlMatch[i].match(ctx) ) {
427                 matched[i]=true;
428                 matchCount++;
429             }
430         }
431         return matchCount;
432     }
433
434
435     private static String capitalize(String name) {
436         if (name == null || name.length() == 0) {
437             return name;
438         }
439         char chars[] = name.toCharArray();
440         chars[0] = Character.toUpperCase(chars[0]);
441         return new String(chars);
442     }
443
444 }