Source for org.jfree.xml.util.AbstractModelReader

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * ------------------------
  28:  * AbstractModelReader.java
  29:  * ------------------------
  30:  * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: AbstractModelReader.java,v 1.8 2005/10/18 13:33:53 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 12-Nov-2003 : Initial version
  40:  * 25-Nov-2003 : Updated header (DG);
  41:  *
  42:  */
  43: 
  44: package org.jfree.xml.util;
  45: 
  46: import java.io.BufferedInputStream;
  47: import java.io.InputStream;
  48: import java.net.URL;
  49: import java.util.Stack;
  50: 
  51: import javax.xml.parsers.SAXParser;
  52: import javax.xml.parsers.SAXParserFactory;
  53: 
  54: import org.jfree.util.Log;
  55: import org.jfree.util.ObjectUtilities;
  56: import org.jfree.xml.CommentHandler;
  57: import org.jfree.xml.ElementDefinitionException;
  58: import org.xml.sax.Attributes;
  59: import org.xml.sax.InputSource;
  60: import org.xml.sax.SAXException;
  61: import org.xml.sax.XMLReader;
  62: import org.xml.sax.helpers.DefaultHandler;
  63: 
  64: /**
  65:  * Loads the class model from an previously written xml file set.
  66:  * This class provides abstract methods which get called during the parsing
  67:  * (similiar to the SAX parsing, but slightly easier to code).
  68:  *
  69:  * This will need a rewrite in the future, when the structure is finished.
  70:  */
  71: public abstract class AbstractModelReader {
  72: 
  73:     /** The 'START' state. */
  74:     private static final int STATE_START = 0;
  75:     
  76:     /** The 'IN_OBJECT' state. */
  77:     private static final int IN_OBJECT = 1;
  78: 
  79:     /** The 'IGNORE_OBJECT' state. */
  80:     private static final int IGNORE_OBJECT = 2;
  81:     
  82:     /** The 'MAPPING' state. */
  83:     private static final int MAPPING_STATE = 3;
  84:     
  85:     /** The 'CONSTRUCTOR' state. */
  86:     private static final int CONSTRUCTOR_STATE = 4;
  87: 
  88:     /**
  89:      * The SAX2 callback implementation used for parsing the model xml files.
  90:      */
  91:     private class SAXModelHandler extends DefaultHandler {
  92: 
  93:         /** The resource URL. */
  94:         private URL resource;
  95: 
  96:         /** The current state. */
  97:         private int state;
  98:         
  99:         /** 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: }