001 /* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ------------------------
028 * AbstractModelReader.java
029 * ------------------------
030 * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
031 *
032 * Original Author: Thomas Morgner;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: AbstractModelReader.java,v 1.8 2005/10/18 13:33:53 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 12-Nov-2003 : Initial version
040 * 25-Nov-2003 : Updated header (DG);
041 *
042 */
043
044 package org.jfree.xml.util;
045
046 import java.io.BufferedInputStream;
047 import java.io.InputStream;
048 import java.net.URL;
049 import java.util.Stack;
050
051 import javax.xml.parsers.SAXParser;
052 import javax.xml.parsers.SAXParserFactory;
053
054 import org.jfree.util.Log;
055 import org.jfree.util.ObjectUtilities;
056 import org.jfree.xml.CommentHandler;
057 import org.jfree.xml.ElementDefinitionException;
058 import org.xml.sax.Attributes;
059 import org.xml.sax.InputSource;
060 import org.xml.sax.SAXException;
061 import org.xml.sax.XMLReader;
062 import org.xml.sax.helpers.DefaultHandler;
063
064 /**
065 * Loads the class model from an previously written xml file set.
066 * This class provides abstract methods which get called during the parsing
067 * (similiar to the SAX parsing, but slightly easier to code).
068 *
069 * This will need a rewrite in the future, when the structure is finished.
070 */
071 public abstract class AbstractModelReader {
072
073 /** The 'START' state. */
074 private static final int STATE_START = 0;
075
076 /** The 'IN_OBJECT' state. */
077 private static final int IN_OBJECT = 1;
078
079 /** The 'IGNORE_OBJECT' state. */
080 private static final int IGNORE_OBJECT = 2;
081
082 /** The 'MAPPING' state. */
083 private static final int MAPPING_STATE = 3;
084
085 /** The 'CONSTRUCTOR' state. */
086 private static final int CONSTRUCTOR_STATE = 4;
087
088 /**
089 * The SAX2 callback implementation used for parsing the model xml files.
090 */
091 private class SAXModelHandler extends DefaultHandler {
092
093 /** The resource URL. */
094 private URL resource;
095
096 /** The current state. */
097 private int state;
098
099 /** Open comments. */
100 private Stack openComments;
101
102 /** Flag to track includes. */
103 private boolean isInclude;
104
105 /**
106 * Creates a new SAX handler for parsing the model.
107 *
108 * @param resource the resource URL.
109 * @param isInclude an include?
110 */
111 public SAXModelHandler(final URL resource, final boolean isInclude) {
112 if (resource == null) {
113 throw new NullPointerException();
114 }
115 this.resource = resource;
116 this.openComments = new Stack();
117 this.isInclude = isInclude;
118 }
119
120 /**
121 * Receive notification of the start of an element.
122 *
123 * @param uri The Namespace URI, or the empty string if the
124 * element has no Namespace URI or if Namespace
125 * processing is not being performed.
126 * @param localName The local name (without prefix), or the
127 * empty string if Namespace processing is not being
128 * performed.
129 * @param qName The qualified name (with prefix), or the
130 * empty string if qualified names are not available.
131 * @param attributes The attributes attached to the element. If
132 * there are no attributes, it shall be an empty
133 * Attributes object.
134 * @exception SAXException Any SAX exception, possibly
135 * wrapping another exception.
136 *
137 * @see org.xml.sax.ContentHandler#startElement
138 */
139 public void startElement(final String uri, final String localName,
140 final String qName, final Attributes attributes)
141 throws SAXException {
142
143 setOpenComment(getCommentHandler().getComments());
144 this.openComments.push(getOpenComment());
145 setCloseComment(null);
146
147 try {
148
149 if (!this.isInclude && qName.equals(ClassModelTags.OBJECTS_TAG)) {
150 //Log.debug ("Open Comments: " + openComment);
151 startRootDocument();
152 return;
153 }
154
155 if (getState() == STATE_START) {
156 startRootElement(qName, attributes);
157 }
158 else if (getState() == IGNORE_OBJECT) {
159 return;
160 }
161 else if (getState() == IN_OBJECT) {
162 startObjectElement(qName, attributes);
163 }
164 else if (getState() == MAPPING_STATE) {
165 if (!qName.equals(ClassModelTags.TYPE_TAG)) {
166 throw new SAXException("Expected 'type' tag");
167 }
168 final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
169 final String target = attributes.getValue(ClassModelTags.CLASS_ATTR);
170 handleMultiplexMapping(name, target);
171 }
172 else if (getState() == CONSTRUCTOR_STATE) {
173 if (!qName.equals(ClassModelTags.PARAMETER_TAG)) {
174 throw new SAXException("Expected 'parameter' tag");
175 }
176 final String parameterClass = attributes.getValue(ClassModelTags.CLASS_ATTR);
177 final String tagName = attributes.getValue(ClassModelTags.PROPERTY_ATTR); // optional
178 handleConstructorDefinition(tagName, parameterClass);
179 }
180 }
181 catch (ObjectDescriptionException e) {
182 throw new SAXException(e);
183 }
184 finally {
185 getCommentHandler().clearComments();
186 }
187 }
188
189 /**
190 * Receive notification of the end of an element.
191 *
192 * @param uri The Namespace URI, or the empty string if the
193 * element has no Namespace URI or if Namespace
194 * processing is not being performed.
195 * @param localName The local name (without prefix), or the
196 * empty string if Namespace processing is not being
197 * performed.
198 * @param qName The qualified name (with prefix), or the
199 * empty string if qualified names are not available.
200 * @exception SAXException Any SAX exception, possibly
201 * wrapping another exception.
202 * @see org.xml.sax.ContentHandler#endElement
203 */
204 public void endElement(final String uri, final String localName, final String qName)
205 throws SAXException {
206
207 setOpenComment((String[]) this.openComments.pop());
208 setCloseComment(getCommentHandler().getComments());
209
210 try {
211 if (!this.isInclude && qName.equals(ClassModelTags.OBJECTS_TAG)) {
212 endRootDocument();
213 return;
214 }
215
216 if (qName.equals(ClassModelTags.OBJECT_TAG)) {
217 if (getState() != IGNORE_OBJECT) {
218 endObjectDefinition();
219 }
220 setState(STATE_START);
221 }
222 else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
223 setState(STATE_START);
224 endMultiplexMapping();
225 }
226 else if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
227 if (getState() != IGNORE_OBJECT) {
228 setState(IN_OBJECT);
229 }
230 }
231 }
232 catch (ObjectDescriptionException e) {
233 throw new SAXException(e);
234 }
235 finally {
236 getCommentHandler().clearComments();
237 }
238 }
239
240 /**
241 * Handles the start of an element within an object definition.
242 *
243 * @param qName The qualified name (with prefix), or the
244 * empty string if qualified names are not available.
245 * @param attributes The attributes attached to the element. If
246 * there are no attributes, it shall be an empty
247 * Attributes object.
248 * @throws ObjectDescriptionException if an error occured while
249 * handling this tag
250 */
251 private void startObjectElement(final String qName, final Attributes attributes)
252 throws ObjectDescriptionException {
253 if (qName.equals(ClassModelTags.CONSTRUCTOR_TAG)) {
254 setState(CONSTRUCTOR_STATE);
255 }
256 else if (qName.equals(ClassModelTags.LOOKUP_PROPERTY_TAG)) {
257 final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
258 final String lookupKey = attributes.getValue(ClassModelTags.LOOKUP_ATTR);
259 handleLookupDefinition(name, lookupKey);
260 }
261 else if (qName.equals(ClassModelTags.IGNORED_PROPERTY_TAG)) {
262 final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
263 handleIgnoredProperty(name);
264 }
265 else if (qName.equals(ClassModelTags.ELEMENT_PROPERTY_TAG)) {
266 final String elementAtt = attributes.getValue(ClassModelTags.ELEMENT_ATTR);
267 final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
268 handleElementDefinition(name, elementAtt);
269 }
270 else if (qName.equals(ClassModelTags.ATTRIBUTE_PROPERTY_TAG)) {
271 final String name = attributes.getValue(ClassModelTags.NAME_ATTR);
272 final String attribName = attributes.getValue(ClassModelTags.ATTRIBUTE_ATTR);
273 final String handler = attributes.getValue(ClassModelTags.ATTRIBUTE_HANDLER_ATTR);
274 handleAttributeDefinition(name, attribName, handler);
275 }
276 }
277
278 /**
279 * Handles the include or object tag.
280 *
281 * @param qName The qualified name (with prefix), or the
282 * empty string if qualified names are not available.
283 * @param attributes The attributes attached to the element. If
284 * there are no attributes, it shall be an empty
285 * Attributes object.
286 * @throws SAXException if an parser error occured
287 * @throws ObjectDescriptionException if an object model related
288 * error occured.
289 */
290 private void startRootElement(final String qName, final Attributes attributes)
291 throws SAXException, ObjectDescriptionException {
292
293 if (qName.equals(ClassModelTags.INCLUDE_TAG)) {
294 if (this.isInclude) {
295 Log.warn("Ignored nested include tag.");
296 return;
297 }
298 final String src = attributes.getValue(ClassModelTags.SOURCE_ATTR);
299 try {
300 final URL url = new URL(this.resource, src);
301 startIncludeHandling(url);
302 parseXmlDocument(url, true);
303 endIncludeHandling();
304 }
305 catch (Exception ioe) {
306 throw new ElementDefinitionException
307 (ioe, "Unable to include file from " + src);
308 }
309 }
310 else if (qName.equals(ClassModelTags.OBJECT_TAG)) {
311 setState(IN_OBJECT);
312 final String className = attributes.getValue(ClassModelTags.CLASS_ATTR);
313 String register = attributes.getValue(ClassModelTags.REGISTER_NAMES_ATTR);
314 if (register != null && register.length() == 0) {
315 register = null;
316 }
317 final boolean ignored = "true".equals(attributes.getValue(ClassModelTags.IGNORE_ATTR));
318 if (!startObjectDefinition(className, register, ignored)) {
319 setState(IGNORE_OBJECT);
320 }
321 }
322 else if (qName.equals(ClassModelTags.MANUAL_TAG)) {
323 final String className = attributes.getValue(ClassModelTags.CLASS_ATTR);
324 final String readHandler = attributes.getValue(ClassModelTags.READ_HANDLER_ATTR);
325 final String writeHandler = attributes.getValue(ClassModelTags.WRITE_HANDLER_ATTR);
326 handleManualMapping(className, readHandler, writeHandler);
327 }
328 else if (qName.equals(ClassModelTags.MAPPING_TAG)) {
329 setState(MAPPING_STATE);
330 final String typeAttr = attributes.getValue(ClassModelTags.TYPE_ATTR);
331 final String baseClass = attributes.getValue(ClassModelTags.BASE_CLASS_ATTR);
332 startMultiplexMapping(baseClass, typeAttr);
333 }
334 }
335
336 /**
337 * Returns the current state.
338 *
339 * @return the state.
340 */
341 private int getState() {
342 return this.state;
343 }
344
345 /**
346 * Sets the current state.
347 *
348 * @param state the state.
349 */
350 private void setState(final int state) {
351 this.state = state;
352 }
353 }
354
355 /** The comment handler. */
356 private CommentHandler commentHandler;
357
358 /** The close comments. */
359 private String[] closeComment;
360
361 /** The open comments. */
362 private String[] openComment;
363
364 /**
365 * Default Constructor.
366 */
367 public AbstractModelReader() {
368 this.commentHandler = new CommentHandler();
369 }
370
371 /**
372 * Returns the comment handler.
373 *
374 * @return The comment handler.
375 */
376 protected CommentHandler getCommentHandler() {
377 return this.commentHandler;
378 }
379
380 /**
381 * Returns the close comment.
382 *
383 * @return The close comment.
384 */
385 protected String[] getCloseComment() {
386 return this.closeComment;
387 }
388
389 /**
390 * Returns the open comment.
391 *
392 * @return The open comment.
393 */
394 protected String[] getOpenComment() {
395 return this.openComment;
396 }
397
398 /**
399 * Sets the close comment.
400 *
401 * @param closeComment the close comment.
402 */
403 protected void setCloseComment(final String[] closeComment) {
404 this.closeComment = closeComment;
405 }
406
407 /**
408 * Sets the open comment.
409 *
410 * @param openComment the open comment.
411 */
412 protected void setOpenComment(final String[] openComment) {
413 this.openComment = openComment;
414 }
415
416 /**
417 * Parses an XML document at the given URL.
418 *
419 * @param resource the document URL.
420 *
421 * @throws ObjectDescriptionException ??
422 */
423 protected void parseXml(final URL resource) throws ObjectDescriptionException {
424 parseXmlDocument(resource, false);
425 }
426
427 /**
428 * Parses the given specification and loads all includes specified in the files.
429 * This implementation does not check for loops in the include files.
430 *
431 * @param resource the url of the xml specification.
432 * @param isInclude an include?
433 *
434 * @throws org.jfree.xml.util.ObjectDescriptionException if an error occured which prevented the
435 * loading of the specifications.
436 */
437 protected void parseXmlDocument(final URL resource, final boolean isInclude)
438 throws ObjectDescriptionException {
439
440 try {
441 final InputStream in = new BufferedInputStream(resource.openStream());
442 final SAXParserFactory factory = SAXParserFactory.newInstance();
443 final SAXParser saxParser = factory.newSAXParser();
444 final XMLReader reader = saxParser.getXMLReader();
445
446 final SAXModelHandler handler = new SAXModelHandler(resource, isInclude);
447 try {
448 reader.setProperty
449 ("http://xml.org/sax/properties/lexical-handler",
450 getCommentHandler());
451 }
452 catch (SAXException se) {
453 Log.debug("Comments are not supported by this SAX implementation.");
454 }
455 reader.setContentHandler(handler);
456 reader.setDTDHandler(handler);
457 reader.setErrorHandler(handler);
458 reader.parse(new InputSource(in));
459 in.close();
460 }
461 catch (Exception e) {
462 // unable to init
463 Log.warn("Unable to load factory specifications", e);
464 throw new ObjectDescriptionException("Unable to load object factory specs.", e);
465 }
466 }
467
468 /**
469 * Start the root document.
470 */
471 protected void startRootDocument() {
472 // nothing required
473 }
474
475 /**
476 * End the root document.
477 */
478 protected void endRootDocument() {
479 // nothing required
480 }
481
482 /**
483 * Start handling an include.
484 *
485 * @param resource the URL.
486 */
487 protected void startIncludeHandling(final URL resource) {
488 // nothing required
489 }
490
491 /**
492 * End handling an include.
493 */
494 protected void endIncludeHandling() {
495 // nothing required
496 }
497
498 /**
499 * Callback method for ignored properties. Such properties get marked so that
500 * the information regarding these properties won't get lost.
501 *
502 * @param name the name of the ignored property.
503 */
504 protected void handleIgnoredProperty(final String name) {
505 // nothing required
506 }
507
508 /**
509 * Handles a manual mapping definition. The manual mapping maps specific
510 * read and write handlers to a given base class. Manual mappings always
511 * override any other definition.
512 *
513 * @param className the base class name
514 * @param readHandler the class name of the read handler
515 * @param writeHandler the class name of the write handler
516 * @return true, if the mapping was accepted, false otherwise.
517 * @throws ObjectDescriptionException if an unexpected error occured.
518 */
519 protected abstract boolean handleManualMapping(String className, String readHandler,
520 String writeHandler) throws ObjectDescriptionException;
521
522 /**
523 * Starts a object definition. The object definition collects all properties of
524 * an bean-class and defines, which constructor should be used when creating the
525 * class.
526 *
527 * @param className the class name of the defined object
528 * @param register the (optional) register name, to lookup and reference the object
529 * later.
530 * @param ignored ??.
531 *
532 * @return true, if the definition was accepted, false otherwise.
533 * @throws ObjectDescriptionException if an unexpected error occured.
534 */
535 protected abstract boolean startObjectDefinition(String className, String register,
536 boolean ignored) throws ObjectDescriptionException;
537
538 /**
539 * Handles an attribute definition. This method gets called after the object definition
540 * was started. The method will be called for every defined attribute property.
541 *
542 * @param name the name of the property
543 * @param attribName the xml-attribute name to use later.
544 * @param handlerClass the attribute handler class.
545 * @throws ObjectDescriptionException if an error occured.
546 */
547 protected abstract void handleAttributeDefinition(String name, String attribName,
548 String handlerClass)
549 throws ObjectDescriptionException;
550
551 /**
552 * Handles an element definition. This method gets called after the object definition
553 * was started. The method will be called for every defined element property. Element
554 * properties are used to describe complex objects.
555 *
556 * @param name the name of the property
557 * @param element the xml-tag name for the child element.
558 * @throws ObjectDescriptionException if an error occurs.
559 */
560 protected abstract void handleElementDefinition(String name, String element)
561 throws ObjectDescriptionException;
562
563 /**
564 * Handles an lookup definition. This method gets called after the object definition
565 * was started. The method will be called for every defined lookup property. Lookup properties
566 * reference previously created object using the object's registry name.
567 *
568 * @param name the property name of the base object
569 * @param lookupKey the register key of the referenced object
570 * @throws ObjectDescriptionException if an error occured.
571 */
572 protected abstract void handleLookupDefinition(String name, String lookupKey)
573 throws ObjectDescriptionException;
574
575 /**
576 * Finializes the object definition.
577 *
578 * @throws ObjectDescriptionException if an error occures.
579 */
580 protected abstract void endObjectDefinition() throws ObjectDescriptionException;
581
582 /**
583 * Starts a multiplex mapping. Multiplex mappings are used to define polymorphic
584 * argument handlers. The mapper will collect all derived classes of the given
585 * base class and will select the corresponding mapping based on the given type
586 * attribute.
587 *
588 * @param className the base class name
589 * @param typeAttr the xml-attribute name containing the mapping key
590 */
591 protected abstract void startMultiplexMapping(String className, String typeAttr);
592
593 /**
594 * Defines an entry for the multiplex mapping. The new entry will be activated
595 * when the base mappers type attribute contains this <code>typename</code> and
596 * will resolve to the handler for the given classname.
597 *
598 * @param typeName the type value for this mapping.
599 * @param className the class name to which this mapping resolves.
600 * @throws ObjectDescriptionException if an error occurs.
601 */
602 protected abstract void handleMultiplexMapping(String typeName, String className)
603 throws ObjectDescriptionException;
604
605 /**
606 * Finializes the multiplexer mapping.
607 *
608 * @throws ObjectDescriptionException if an error occurs.
609 */
610 protected abstract void endMultiplexMapping() throws ObjectDescriptionException;
611
612 /**
613 * Handles a constructor definition. Only one constructor can be defined for
614 * a certain object type. The constructor will be filled using the given properties.
615 *
616 * @param propertyName the property name of the referenced local property
617 * @param parameterClass the parameter class for the parameter.
618 * @throws ObjectDescriptionException if an error occured.
619 */
620 protected abstract void handleConstructorDefinition(String propertyName, String parameterClass)
621 throws ObjectDescriptionException;
622
623 /**
624 * Loads the given class, and ignores all exceptions which may occur
625 * during the loading. If the class was invalid, null is returned instead.
626 *
627 * @param className the name of the class to be loaded.
628 * @return the class or null.
629 */
630 protected Class loadClass(final String className) {
631 if (className == null) {
632 return null;
633 }
634 if (className.startsWith("::")) {
635 return BasicTypeSupport.getClassRepresentation(className);
636 }
637 try {
638 return ObjectUtilities.getClassLoader(getClass()).loadClass(className);
639 }
640 catch (Exception e) {
641 // ignore buggy classes for now ..
642 Log.warn("Unable to load class", e);
643 return null;
644 }
645 }
646
647 }