2 * Copyright (C) 2001, 2002 The Mir-coders group
4 * This file is part of Mir.
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.
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.
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
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.
36 import java.lang.reflect.*;
37 import org.xml.sax.helpers.DefaultHandler;
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.parsers.SAXParser;
41 import javax.xml.parsers.SAXParserFactory;
44 import mir.misc.ConfigException;
45 import mir.misc.Location;
48 * Configures a based on
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.
59 * much code is stolen from ant ProjectHelper.java.
61 * @author -mh <heckmann@hbe.ca>
65 public class XmlConfigurator {
67 private static SAXParserFactory parserFactory = null;
69 private SAXParser saxParser;
70 //private Project project;
71 private File configFile;
72 private File configFileParent;
73 private Locator locator;
75 private SaxContext saxContext;
77 XmlMatch requiredXmlMatch[]=new XmlMatch[256]; //maximum amount of rules
78 int requiredXmlMatchCount=0;
79 boolean matched[] = new boolean[256];
82 XmlMatch mustComeFirstMatch[]=new XmlMatch[256]; //maximum amount of rules
83 int comeFirstMatchCount=0;
85 Property comesFirstArr[]=new Property[128];
86 int comesFirstCount=0;
88 Property propertyArr[]=new Property[128];
91 private static XmlConfigurator instance = new XmlConfigurator();
92 public static XmlConfigurator getInstance() { return instance; }
95 * Configures the Project with the contents of the specified XML file.
97 public void configure(File configFile) throws ConfigException {
98 setConfigFile(configFile);
103 * konstruktor. private so no one calls "new" on us.
105 private XmlConfigurator() {}
109 * Constructs a new Ant parser for the specified XML file.
111 private void setConfigFile(File configFile) {
112 this.configFile = new File(configFile.getAbsolutePath());
113 configFileParent = new File(this.configFile.getParent());
114 saxContext = new SaxContext();
118 * Parses the config file.
120 private void parse() throws ConfigException {
121 FileInputStream inputStream = null;
122 InputSource inputSource = null;
125 SAXParserFactory factory = SAXParserFactory.newInstance();
126 saxParser = factory.newSAXParser();
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);
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++) {
140 throw new ConfigException("Error parsing config file, missing required element: "+requiredXmlMatch[i].toString());
144 for(int i=0; i<comesFirstCount;i++) {
145 comesFirstArr[i].set();
147 for(int i=0; i<propertyCount;i++) {
148 propertyArr[i].set();
149 System.out.println("about to set: "+i);
151 } catch (Exception e) {
152 throw new SAXParseException(e.toString(), locator);
155 catch(ParserConfigurationException exc) {
156 throw new ConfigException("Parser has not been configured correctly", exc);
158 catch(SAXParseException exc) {
160 new Location(configFile.toString(), exc.getLineNumber(), exc.getColumnNumber());
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);
171 throw new ConfigException(exc.getMessage(), t, location);
173 catch(SAXException exc) {
174 Throwable t = exc.getException();
175 if (t instanceof ConfigException) {
176 throw (ConfigException) t;
178 throw new ConfigException(exc.getMessage(), t);
180 catch(FileNotFoundException exc) {
181 throw new ConfigException(exc);
183 catch(IOException exc) {
184 throw new ConfigException("Error reading config file", exc);
187 if (inputStream != null) {
191 catch (IOException ioe) {
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.
203 * Each type of xml element (task, target, etc) in ant will
204 * have its own subclass of AbstractHandler.
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.
210 private class AbstractHandler extends DefaultHandler {
211 protected ContentHandler parentHandler;
213 public AbstractHandler(ContentHandler parentHandler) {
214 this.parentHandler = parentHandler;
216 // Start handling SAX events
218 saxParser.getXMLReader().setContentHandler(this);
219 } catch (SAXException e) {
220 throw new ConfigException("Error getting XMLReader",e);
225 public void startElement(String uri, String tag, String qName, Attributes attrs) throws SAXParseException {
226 throw new SAXParseException("Unexpected element \"" + tag + "\"", locator);
229 public void characters(char[] buf, int start, int end) throws SAXParseException {
230 String s = new String(buf, start, end).trim();
232 if (s.length() > 0) {
233 throw new SAXParseException("Unexpected text \"" + s + "\"", locator);
238 * Called when this element and all elements nested into it have been
241 protected void finished() {}
243 public void endElement(String uri, String tag, String qName) throws SAXException {
245 // Let parent resume handling SAX events
246 saxParser.getXMLReader().setContentHandler(parentHandler);
251 * Handler for the root element. It's only child must be the "mir" element.
253 private class RootHandler extends DefaultHandler {
256 * resolve file: URIs as relative to the build file.
258 public InputSource resolveEntity(String publicId,
262 if (systemId.startsWith("file:")) {
263 String path = systemId.substring(5);
264 int index = path.indexOf("file:");
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:");
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");
281 File file = new File(path);
282 if (!file.isAbsolute()) {
283 file = new File(configFileParent, path);
287 InputSource inputSource = new InputSource(new FileInputStream(file));
288 inputSource.setSystemId("file:" + entitySystemId);
290 } catch (FileNotFoundException fne) {
291 System.out.println(file.getAbsolutePath()+" could not be found");
294 // use default if not file or file not found
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);
302 throw new SAXParseException("Config file is not of expected XML type", locator);
306 public void setDocumentLocator(Locator locator) {
307 XmlConfigurator.this.locator = locator;
312 * Handler for the top level "project" element.
314 private class MirHandler extends AbstractHandler {
315 public MirHandler(ContentHandler parentHandler) {
316 super(parentHandler);
319 public void init(String tag, Attributes attrs) throws SAXParseException {
322 for (int i = 0; i < attrs.getLength(); i++) {
323 String key = attrs.getLocalName(i);
324 String value = attrs.getValue(i);
326 if (key.equals("name")) {
329 throw new SAXParseException("Unexpected attribute \"" + attrs.getLocalName(i) + "\"", locator);
334 throw new SAXParseException("The default attribute of \"name\" is required",
338 saxContext.push(tag);
339 matchedCount += checkRequiredTag(saxContext);
341 //MirConfig.setName(name);
345 public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
346 if (name.equals("class")) {
347 handleClassdef(name, attrs);
349 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
353 public void finished() {
354 System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
358 private void handleClassdef(String name, Attributes attrs) throws SAXParseException {
359 (new ClassHandler(this)).init(name, attrs);
365 * Handler for "class" elements.
367 private class ClassHandler extends AbstractHandler {
371 public ClassHandler(ContentHandler parentHandler) {
372 super(parentHandler);
375 public void init(String tag, Attributes attrs) throws SAXParseException {
378 for (int i = 0; i < attrs.getLength(); i++) {
379 String key = attrs.getLocalName(i);
380 String value = attrs.getValue(i);
382 if (key.equals("name")) {
385 throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
390 throw new SAXParseException("class element appears without a \"name\" attribute", locator);
393 saxContext.push(tag+":"+name);
394 matchedCount += checkRequiredTag(saxContext);
396 classN=Class.forName(name, false, this.getClass().getClassLoader());
397 } catch (ClassNotFoundException e) {
398 throw new ConfigException("Error invoking class: \""+name+
404 public void startElement(String uri, String name, String qName, Attributes attrs) throws SAXParseException {
405 if (name.equals("property")) {
406 handleProperties(name, attrs);
408 throw new SAXParseException("Unexpected element \"" + name + "\"", locator);
412 public void finished() {
413 System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
414 System.out.println("COUNT "+saxContext.getTagCount());
418 private void handleProperties(String name, Attributes attrs) throws SAXParseException {
419 (new PropertiesHandler(this, classN )).init(name, attrs);
425 * Handler for all property elements.
427 private class PropertiesHandler extends AbstractHandler {
428 private Class classN;
430 public PropertiesHandler(ContentHandler parentHandler, Class classN) {
431 super(parentHandler);
433 this.classN = classN;
436 public void init(String tag, Attributes attrs) throws SAXParseException {
440 for (int i = 0; i < attrs.getLength(); i++) {
441 String key = attrs.getLocalName(i);
442 String v = attrs.getValue(i);
444 if (key.equals("name")) {
446 } else if (key.equals("value")) {
449 throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator);
454 throw new SAXParseException("property element appears without a \"name\" attribute", locator);
457 throw new SAXParseException("property element appears without a \"value\" attribute", locator);
459 saxContext.push(tag+":"+name);
460 matchedCount += checkRequiredTag(saxContext);
462 //finally add it to the lists
463 //to be processed later
464 if (checkComesFirstTag(saxContext)) {
465 comesFirstArr[comesFirstCount]=new Property(classN, name, value);
468 propertyArr[propertyCount]=new Property(classN, name, value);
473 protected void finished() {
474 System.out.println("COUNT "+saxContext.getTagCount()+" TAG "+saxContext.getTag(saxContext.getTagCount()-1));
475 System.out.println("COUNT "+saxContext.getTagCount());
481 public void addComesFirstTag(String xmlPath) {
482 mustComeFirstMatch[comeFirstMatchCount]=new XmlMatch(xmlPath);
483 comeFirstMatchCount++;
486 private boolean checkComesFirstTag(SaxContext ctx) {
487 for( int i=0; i<comeFirstMatchCount; i++ ) {
488 if( mustComeFirstMatch[i].match(ctx) ) {
495 public void addRequiredTag(String xmlPath) {
496 requiredXmlMatch[requiredXmlMatchCount]=new XmlMatch(xmlPath);
497 matched[requiredXmlMatchCount]=false;
498 requiredXmlMatchCount++;
501 private int checkRequiredTag(SaxContext ctx) {
503 for( int i=0; i<requiredXmlMatchCount; i++ ) {
504 if( requiredXmlMatch[i].match(ctx) ) {
513 private static String capitalize(String name) {
514 if (name == null || name.length() == 0) {
517 char chars[] = name.toCharArray();
518 chars[0] = Character.toUpperCase(chars[0]);
519 return new String(chars);
522 private class Property {
527 public Property( Class classN, String name, String value) {
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).
537 public void set() throws Exception {
539 String setter= "set" +capitalize(name);
542 Method methods[]=classN.getMethods();
543 Method setPropertyMethod=null;
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())) {
552 methods[i].invoke( null, new Object[] { value } );
557 // Try a setFoo ( int ), (float) or ( boolean )
558 for( int i=0; i< methods.length; i++ ) {
560 if( setter.equals( methods[i].getName() ) &&
561 methods[i].getParameterTypes().length == 1) {
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())) {
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())) {
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);
580 throw new Exception("Unknown type " + paramType.getName() + "for property \""+name+"\"with value \""+value+"\"");
584 System.out.println("XXX: " + methods[i] + " " + classN + " " + params[0] );
585 methods[i].invoke( null, params );
591 //if we got this far it means we were not successful in setting the
593 throw new Exception("Count not find method \""+setter+"\" in Class \""+classN.getName()+"\" in order to set property \""+name+"\"");
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();