View Javadoc
1 /* 2 * GraphBuilder.java 3 * Created on September 18, 2003 4 * 5 * The Blues Framework - A lightweight application framework 6 * Copyright (C) 2003 Lonnie Pryor 7 * http://blues.lonniepryor.com 8 * 9 * This library is free software; you can redistribute it and/or modify it under the 10 * terms of the GNU Lesser General Public License as published by the Free Software 11 * Foundation; either version 2.1 of the License, or (at your option) any later 12 * version. 13 * 14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 16 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License along 19 * with this library; if not, write to: 20 * 21 * The Free Software Foundation, Inc. 22 * 59 Temple Place, Suite 330 23 * Boston, MA 02111-1307 USA 24 * 25 */ 26 package com.lonniepryor.blues.xml; 27 28 import java.io.IOException; 29 30 import java.net.URL; 31 32 import java.util.HashMap; 33 import java.util.LinkedList; 34 import java.util.Map; 35 36 import javax.xml.parsers.SAXParser; 37 38 import com.lonniepryor.blues.cfg.TextContainer; 39 import com.lonniepryor.blues.cfg.Verifiable; 40 import com.lonniepryor.blues.util.Strings; 41 42 import org.xml.sax.Attributes; 43 import org.xml.sax.Locator; 44 import org.xml.sax.SAXException; 45 import org.xml.sax.SAXParseException; 46 import org.xml.sax.helpers.DefaultHandler; 47 48 /*** 49 * Handles transforming a single XML document into a graph of objects. NOTE: this 50 * class is NOT thread-safe and instances may only be used on a single thread at a 51 * time. 52 * 53 * @author Lonnie Pryor 54 * @version $Revision: 1.1 $ 55 */ 56 public class GraphBuilder { 57 /*** The URI of the Blues namespace. */ 58 public static final String bluesNamespaceUri = "http://blues.lonniepryor.com"; 59 /*** The class loader to load element classes from. */ 60 private final ClassLoader classLoader; 61 /*** Cache of xml bean helpers. */ 62 private final Map xmlBeanHelpers = new HashMap(); 63 /*** The current relative path base. */ 64 private String currentPathBase = null; 65 /*** The current value resolver. */ 66 private ValueResolver resolver = null; 67 /*** The stack of element objects. */ 68 private LinkedList stack = new LinkedList(); 69 /*** The object representing the root element of the document. */ 70 private Object result = null; 71 72 /*** 73 * Creates a new GraphBuilder object. 74 * 75 * @param classLoader The class loader to load element classes from. 76 */ 77 public GraphBuilder (ClassLoader classLoader) { 78 if (classLoader == null) 79 throw new NullPointerException("classLoader"); 80 this.classLoader = classLoader; 81 } 82 83 /*** 84 * Parses the XML document at the specified URL, transforming it into a graph of 85 * objects. 86 * 87 * @param parser The parser to use. 88 * @param documentPath The location of the document to parse. 89 * 90 * @return The object generated for the root element of the document. 91 */ 92 public Object buildGraph (SAXParser parser, String documentPath) { 93 if (parser == null) 94 throw new NullPointerException("parser"); 95 if (documentPath == null) 96 throw new NullPointerException("documentPath"); 97 URL resource = classLoader.getResource(documentPath); 98 if (resource == null) 99 throw new XmlException("XML document not found at '" + documentPath + "'"); 100 Object thisResult = null; 101 try { 102 currentPathBase = documentPath.substring( 103 0, documentPath.lastIndexOf('/') + 1); 104 parser.parse(resource.openStream(), newDocumentHandler()); 105 thisResult = result; 106 } catch (SAXParseException spe) { 107 throw new XmlException( 108 "Parse error: " + resource.toExternalForm() + " line " 109 + spe.getLineNumber() + " column " + spe.getColumnNumber() + ": " 110 + spe.getMessage(), spe); 111 } catch (SAXException se) { 112 throw new XmlException( 113 "XML error '" + resource.toExternalForm() + "': " + se.getMessage(), se); 114 } catch (IOException ioe) { 115 throw new XmlException( 116 "IO error '" + resource.toExternalForm() + "': " + ioe.getMessage(), ioe); 117 } finally { 118 currentPathBase = null; 119 resolver = null; 120 stack.clear(); 121 result = null; 122 } 123 return thisResult; 124 } 125 126 /*** 127 * Signals the beginning of a prefixed element. 128 * 129 * @param attributes The element attributes. 130 */ 131 private void startPrefixedElement (Attributes attributes) { 132 if (stack.isEmpty()) 133 resolver = new ValueResolver( 134 classLoader, currentPathBase, 135 attributes.getValue(bluesNamespaceUri, "imports")); 136 XmlBeanHelper helper = getHelper( 137 resolver.resolveClass(attributes.getValue(bluesNamespaceUri, "cfg"))); 138 stack.addLast(createElementInstance(helper, attributes)); 139 } 140 141 /*** 142 * Signals the end of a prefixed element. 143 */ 144 private void endPrefixedElement () { 145 if (stack.isEmpty()) 146 throw new IllegalStateException("Prefixed element to end not found"); 147 Object instance = stack.removeLast(); 148 if (instance instanceof Verifiable) 149 ((Verifiable)instance).verify(); 150 if (stack.isEmpty()) 151 result = instance; 152 else 153 getHelper(stack.getLast().getClass()).declarePrefixedElement( 154 stack.getLast(), instance); 155 } 156 157 /*** 158 * Signals the beginning of a local element. 159 * 160 * @param name The name of the element. 161 * @param attributes The element attributes. 162 */ 163 private void startLocalElement (String name, Attributes attributes) { 164 if (stack.isEmpty()) 165 throw new IllegalStateException( 166 "Documents must begin with a prefixed element"); 167 Class elementType = getHelper(stack.getLast().getClass()) 168 .getLocalDeclarationType(transformName(name)); 169 if (elementType == null) 170 throw new IllegalArgumentException( 171 "Local element declaration for '" + name + "' not found on " 172 + stack.getLast().getClass().getName()); 173 stack.addLast(createElementInstance(getHelper(elementType), attributes)); 174 } 175 176 /*** 177 * Signals the end of a local element. 178 * 179 * @param name The name of the element. 180 */ 181 private void endLocalElement (String name) { 182 if (stack.isEmpty()) 183 throw new IllegalStateException("Local element to end not found"); 184 Object instance = stack.removeLast(); 185 if (instance instanceof Verifiable) 186 ((Verifiable)instance).verify(); 187 if (stack.isEmpty()) 188 throw new IllegalStateException("Parent of local element not found"); 189 getHelper(stack.getLast().getClass()).declareLocalElement( 190 stack.getLast(), transformName(name), instance); 191 } 192 193 /*** 194 * Signals the presence of character data. 195 * 196 * @param ch The character text. 197 * @param start The start of the active region. 198 * @param length The length of the active region. 199 */ 200 private void handleCharacters (char[] ch, int start, int length) { 201 Object instance = stack.getLast(); 202 if (instance instanceof TextContainer) 203 ((TextContainer)instance).appendText(ch, start, length); 204 } 205 206 /*** 207 * Finds the xml bean helper for the specified class. 208 * 209 * @param forClass The class to find a helper for. 210 * 211 * @return The requested xml bean helper. 212 */ 213 private XmlBeanHelper getHelper (Class forClass) { 214 XmlBeanHelper helper = (XmlBeanHelper)xmlBeanHelpers.get(forClass); 215 if (helper == null) 216 xmlBeanHelpers.put(forClass, helper = new XmlBeanHelper(forClass)); 217 return helper; 218 } 219 220 /*** 221 * Creates a new element of the specified type and populates its properties with 222 * the supplied attributes. 223 * 224 * @param helper The type of element to create. 225 * @param attrs The attributes to populate properties from. 226 * 227 * @return The new, populated instance. 228 */ 229 private Object createElementInstance (XmlBeanHelper helper, Attributes attrs) { 230 Object instance = helper.newBeanInstance(); 231 for (int i = 0; i < attrs.getLength(); ++i) { 232 if (Strings.notEmpty().isSatisfiedBy(attrs.getURI(i))) 233 continue; 234 String propertyName = transformName(attrs.getQName(i)); 235 Class propertyType = helper.getPropertyType(propertyName); 236 if (propertyType == null) 237 throw new IllegalArgumentException( 238 "Property for '" + attrs.getQName(i) + "' not found on " 239 + instance.getClass().getName()); 240 Object resolved = resolver.resolve(propertyType, attrs.getValue(i)); 241 if (resolved != null) 242 helper.setProperty(instance, propertyName, resolved); 243 else if ( 244 !helper.setPropertyAsString(instance, propertyName, attrs.getValue(i))) 245 throw new IllegalArgumentException( 246 "Cannot set property for '" + attrs.getQName(i) + "'"); 247 } 248 return instance; 249 } 250 251 /*** 252 * Transforms an xml name to a java name by removing each instance of '-' and 253 * capitalizing the character immeadiately following it. 254 * 255 * @param xmlName The xml name to transform. 256 * 257 * @return The transformed name. 258 */ 259 private String transformName (String xmlName) { 260 if (xmlName == null) 261 throw new NullPointerException("xmlName"); 262 StringBuffer javaName = new StringBuffer(xmlName.length()); 263 boolean capitalize = false; 264 for (int i = 0; i < xmlName.length(); ++i) { 265 char current = xmlName.charAt(i); 266 switch (current) { 267 case '-': 268 capitalize = true; 269 break; 270 default: 271 javaName.append(capitalize ? Character.toUpperCase(current) : current); 272 capitalize = false; 273 } 274 } 275 return javaName.toString(); 276 } 277 278 /*** 279 * Creates a new document handler that acts as an exception boundry. 280 * 281 * @return A new document handler that acts as an exception boundry. 282 */ 283 private DefaultHandler newDocumentHandler () { 284 return new DefaultHandler() { 285 Locator locator = null; 286 287 public void setDocumentLocator (Locator locator) { 288 this.locator = locator; 289 } 290 291 public void startElement ( 292 String uri, String localName, String qName, Attributes attributes) 293 throws SAXException { 294 try { 295 if (!Strings.notEmpty().isSatisfiedBy(uri)) 296 startLocalElement(qName, attributes); 297 else 298 startPrefixedElement(attributes); 299 } catch (Exception e) { 300 throw new SAXParseException(e.getMessage(), locator, e); 301 } 302 } 303 304 public void endElement (String uri, String localName, String qName) 305 throws SAXException { 306 try { 307 if (!Strings.notEmpty().isSatisfiedBy(uri)) 308 endLocalElement(qName); 309 else 310 endPrefixedElement(); 311 } catch (Exception e) { 312 throw new SAXParseException(e.getMessage(), locator, e); 313 } 314 } 315 316 public void characters (char[] ch, int start, int length) 317 throws SAXException { 318 try { 319 handleCharacters(ch, start, length); 320 } catch (Exception e) { 321 throw new SAXParseException(e.getMessage(), locator, e); 322 } 323 } 324 325 public void warning (SAXParseException spe) 326 throws SAXException { 327 throw spe; 328 } 329 330 public void error (SAXParseException spe) 331 throws SAXException { 332 throw spe; 333 } 334 335 public void fatalError (SAXParseException spe) 336 throws SAXException { 337 throw spe; 338 } 339 }; 340 } 341 }

This page was automatically generated by Maven