]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Merged branch
authorJeremias Maerki <jeremias@apache.org>
Mon, 14 Apr 2008 11:53:29 +0000 (11:53 +0000)
committerJeremias Maerki <jeremias@apache.org>
Mon, 14 Apr 2008 11:53:29 +0000 (11:53 +0000)
https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ProcessingFeedback
into Trunk.

Changes on branch:
........
  r615153 | jeremias | 2008-01-25 10:07:21 +0100 (Fr, 25 Jan 2008) | 1 line

  Created temporary branch for processing feedback.
........
  r615155 | jeremias | 2008-01-25 10:11:59 +0100 (Fr, 25 Jan 2008) | 1 line

  Initial commit of what I've built already for those who prefer code to minimalistic design docs.
........
  r615278 | jeremias | 2008-01-25 18:25:00 +0100 (Fr, 25 Jan 2008) | 1 line

  EventProducer interfaces now operational.
........
  r615773 | jeremias | 2008-01-28 10:06:16 +0100 (Mo, 28 Jan 2008) | 1 line

  No casting in client code when creating EventProducer instances.
........
  r616242 | vhennebert | 2008-01-29 11:34:45 +0100 (Di, 29 Jan 2008) | 3 lines

  Trick to avoid hard-coding the class name of EventProducer in the source file.
  Feel free to revert if it's not ok.
........
  r616900 | jeremias | 2008-01-30 21:59:31 +0100 (Mi, 30 Jan 2008) | 1 line

  Generate event model XMLs in to the build directory: build/gensrc and build/test-gensrc (the latter is new and needs to be setup as source folder in your IDE!)
........
  r616907 | jeremias | 2008-01-30 22:12:59 +0100 (Mi, 30 Jan 2008) | 1 line

  Added an XMLResourceBundle that uses an XML file instead of a properties file to load the translations. The XML format is the same as for Cocoon's XMLResourceBundle.
........
  r617097 | vhennebert | 2008-01-31 11:53:21 +0100 (Do, 31 Jan 2008) | 2 lines

  Minor typo + slight improvement of Javadoc
........
  r617176 | jeremias | 2008-01-31 19:14:19 +0100 (Do, 31 Jan 2008) | 5 lines

  Renamed FopEvent to Event as suggested by Simon.
  EventProducerCollectorTask.java now reads the EventSeverity from a doclet tag.
  Added generation of EventProducer translations (including simple merging, no validation, yet)
  EventFormatter introduced (only basic functionality, yet).
  Added a simple EventListener implementation that uses EventFormatter to convert the events to human-readable, localized messages that are sent to the log via Commons Logging.
........
  r617362 | jeremias | 2008-02-01 08:18:07 +0100 (Fr, 01 Feb 2008) | 1 line

  Some remaining rename operations based on an earlier discussion.
........
  r617413 | jeremias | 2008-02-01 10:46:26 +0100 (Fr, 01 Feb 2008) | 2 lines

  Extracted formatting functionality into utility class AdvancedMessageFormat.java.
  AdvancedMessageFormat.java now supports conditional sub-groups (delimited by []).
........
  r618682 | jeremias | 2008-02-05 17:07:08 +0100 (Di, 05 Feb 2008) | 1 line

  Add support for special object formatters (where toString() isn't good enough). ATM, it's hard-coded but could later be hooked into dynamic discovery if we have multiple such formatters. The SAX Locator is the only example for now.
........
  r618686 | jeremias | 2008-02-05 17:12:56 +0100 (Di, 05 Feb 2008) | 3 lines

  Hooked most of FONode into the new event mechanism. The FOUserAgent provides a DefaultEventBroadcaster instance.
  If a producer method declares throwing an exception, the event is automatically marked FATAL and the dynamic proxy throws an exception right after notifying the listeners.
  The exceptions are created through the EventExceptionManager. It currently contains only one exception factory for ValidationException. If we need more such factories it's better to register them dynamically. Right now, they're hard-coded.
........
  r619313 | jeremias | 2008-02-07 10:14:15 +0100 (Do, 07 Feb 2008) | 1 line

  Make sure no events are now just silently swallowed because after upgrading a user doesn't know about the event system.
........
  r619314 | jeremias | 2008-02-07 10:14:46 +0100 (Do, 07 Feb 2008) | 1 line

  Log what translation file is being written.
........
  r619320 | jeremias | 2008-02-07 10:31:00 +0100 (Do, 07 Feb 2008) | 2 lines

  FObj hooked into the event system.
  Code reduction using a protected method on FONode to acquire a FOValidationEventProducer.
........
  r619359 | jeremias | 2008-02-07 11:59:19 +0100 (Do, 07 Feb 2008) | 2 lines

  Fop's QName now extends XGCommons' QName to initiate a transition.
  Hooked PropertyList into the event mechanism.
........
  r631252 | jeremias | 2008-02-26 16:24:33 +0100 (Di, 26 Feb 2008) | 1 line

  Removed superfluous warning.
........
  r631268 | jeremias | 2008-02-26 17:08:11 +0100 (Di, 26 Feb 2008) | 1 line

  Deprecated two methods which are a problem for localization. Also helps finding additional spots to switch over to the event mechanism.
........
  r633852 | jeremias | 2008-03-05 15:20:24 +0100 (Mi, 05 Mrz 2008) | 1 line

  Add severity to formatting parameters.
........
  r633855 | jeremias | 2008-03-05 15:21:57 +0100 (Mi, 05 Mrz 2008) | 4 lines

  Added support for additional field styles:
  {<fieldname>,if,<true-text>,<false-text>}
  {<fieldname>,equals,<test-string>,<true-text>,<false-text>}
........
  r633856 | jeremias | 2008-03-05 15:24:04 +0100 (Mi, 05 Mrz 2008) | 2 lines

  Javadocs and TODOs.
  EventListeners can change the event severity.
........
  r633857 | jeremias | 2008-03-05 15:27:08 +0100 (Mi, 05 Mrz 2008) | 4 lines

  Javadocs.
  Moved out event listener registration into a CompositeEventListener.
  Event broadcaster uses the events effective severity, not the initial value (for the case where listeners override the initial value).
  Set up a special EventBroadCaster in the FOUserAgent that filters events through a class (FOValidationEventListenerProxy) that adjusts the event severity for relaxed validation.
........
  r633858 | jeremias | 2008-03-05 15:32:07 +0100 (Mi, 05 Mrz 2008) | 2 lines

  Instead of always decentrally checking whether strict validation is enabled or not, this is now done in a special event listener. The event producer method caller simply indicates whether it can recover from the error condition and continue.
  Started switching to event production in table FOs.
........
  r634027 | jeremias | 2008-03-05 21:58:35 +0100 (Mi, 05 Mrz 2008) | 7 lines

  Moved AdvancedMessageFormat into its own package.
  AdvancedMessageFormat got the following added functionality:
  - Alternative conditional regions [ bla {field}] -> [ bla {field1}| even more bla {field2}]
  - Functions: functions get access to the parameters and they can produce an object that is then formatted ({#gatherContextInfo})
  - "if" and "equals" format moved to top-level classes and added by dynamic registration.
  EventFormatter now supports includes in the form {{includeName}} so you can include other entries from the resource bundle for better reuse.
  Some more events in table code.
........
  r634031 | jeremias | 2008-03-05 22:05:22 +0100 (Mi, 05 Mrz 2008) | 1 line

  SVN Props
........
  r634208 | jeremias | 2008-03-06 11:26:52 +0100 (Do, 06 Mrz 2008) | 2 lines

  Improved context gathering.
  Moved GatherContextInfoFunction to an inner class of FONode to reduce visibilities.
........
  r634209 | jeremias | 2008-03-06 11:28:14 +0100 (Do, 06 Mrz 2008) | 1 line

  Made FOPException localizable.
........
  r634280 | jeremias | 2008-03-06 15:38:30 +0100 (Do, 06 Mrz 2008) | 2 lines

  ExceptionFactory is now dynamically registered.
  More table warnings and errors switch to events.
........
  r634326 | jeremias | 2008-03-06 17:08:16 +0100 (Do, 06 Mrz 2008) | 1 line

  Remaining table FOs switched to events.
........
  r634328 | jeremias | 2008-03-06 17:09:21 +0100 (Do, 06 Mrz 2008) | 1 line

  Deprecated FOP's QName. Mixing with Commons' variant only produces problems.
........
  r634381 | jeremias | 2008-03-06 20:12:57 +0100 (Do, 06 Mrz 2008) | 2 lines

  Made the "invalidChild" event fully localizable by adding a "lookup" field for the optional rule to be displayed.
  And a few switches to the event system.
........
  r634692 | jeremias | 2008-03-07 15:31:43 +0100 (Fr, 07 Mrz 2008) | 1 line

  More FO tree stuff switched to events.
........
  r634712 | jeremias | 2008-03-07 16:19:21 +0100 (Fr, 07 Mrz 2008) | 1 line

  Avoid an NPE that says nothing (ex. could happen if the message template is wrong).
........
  r634738 | jeremias | 2008-03-07 17:38:21 +0100 (Fr, 07 Mrz 2008) | 2 lines

  Non-FO children were not properly run through validation by FOTreeBuilder.
  Unified the way that non-FO elements are validated. Some FOs were already fixed. I now fixed the rest, so foreign elements can occur everywhere.
........
  r637833 | jeremias | 2008-03-17 12:01:41 +0100 (Mo, 17 Mrz 2008) | 1 line

  Exception while cloning for RetrieveMarker to be handled by user as suggested by Andreas.
........
  r637835 | jeremias | 2008-03-17 12:03:31 +0100 (Mo, 17 Mrz 2008) | 1 line

  Throw a RuntimeException of no other Exception class is specified for an event as a fallback if someone just sets the event severity to FATAL.
........
  r637838 | jeremias | 2008-03-17 12:06:10 +0100 (Mo, 17 Mrz 2008) | 1 line

  Throw a meaningful exception when the property name is wrong. Otherwise, there will be an ArrayIndexOutOfBoundsException.
........
  r637859 | jeremias | 2008-03-17 13:35:26 +0100 (Mo, 17 Mrz 2008) | 1 line

  Throw a meaningful exception when the property name is wrong. Otherwise, there will be an ArrayIndexOutOfBoundsException.
........
  r637938 | jeremias | 2008-03-17 16:19:51 +0100 (Mo, 17 Mrz 2008) | 1 line

  Switched pagination package to events.
........
  r637947 | jeremias | 2008-03-17 16:45:16 +0100 (Mo, 17 Mrz 2008) | 1 line

  Removed unlocalizable validation helper methods.
........
  r637952 | jeremias | 2008-03-17 16:59:02 +0100 (Mo, 17 Mrz 2008) | 1 line

  Events on FOTreeBuilder.
........
  r638299 | jeremias | 2008-03-18 11:09:30 +0100 (Di, 18 Mrz 2008) | 2 lines

  Added support for java.util.text's ChoiceFormat to AdvancedMessageFormat.
  Reuse the regexes as constants.
........
  r638302 | jeremias | 2008-03-18 11:17:06 +0100 (Di, 18 Mrz 2008) | 1 line

  Events for inline-level layout managers.
........
  r638774 | jeremias | 2008-03-19 11:17:36 +0100 (Mi, 19 Mrz 2008) | 1 line

  Added DEBUG level.
........
  r638777 | jeremias | 2008-03-19 11:23:40 +0100 (Mi, 19 Mrz 2008) | 3 lines

  Generalized FOValidationEventListenerProxy into FOPEventListenerProxy, the main proxy for FOP's own event manipulation proxy. Done because of support for overflow="hidden" vs. overflow="error-if-overflow".
  Switched block-level layout managers to events.
  Some cleanup along the way.
........
  r639222 | jeremias | 2008-03-20 10:27:34 +0100 (Do, 20 Mrz 2008) | 2 lines

  Some initial work for event forwarding from Batik.
  Missing errors/exceptions converted to events in PageSequenceMaster.
........
  r639270 | jeremias | 2008-03-20 13:50:35 +0100 (Do, 20 Mrz 2008) | 1 line

  Removed DEBUG event severity again. Promoted constrained geometry adjustment event to INFO level as per discussion.
........
  r640395 | jeremias | 2008-03-24 13:39:13 +0100 (Mo, 24 Mrz 2008) | 3 lines

  Moved the creation of the fallback LoggingEventListener to FOUserAgent so event before the startDocument() SAX event arrive in the log.
  Dynamic discovery of event models. Renderers and extensions can register renderer-specific event models.
  Switched the most important parts of the renderers to events (maybe not everything is converted).
........
  r640397 | jeremias | 2008-03-24 13:43:04 +0100 (Mo, 24 Mrz 2008) | 1 line

  Remaining fixcrlfs. Xalan likes to mix CRLF and LF on Windows.
........
  r640398 | jeremias | 2008-03-24 13:43:54 +0100 (Mo, 24 Mrz 2008) | 1 line

  Ignore namespace declarations for property handling.
........
  r640463 | jeremias | 2008-03-24 17:59:52 +0100 (Mo, 24 Mrz 2008) | 2 lines

  Event in area package.
  Exposed getUserAgent() in Renderer interface (was already public in AbstractRenderer).
........
  r642972 | jeremias | 2008-03-31 14:18:39 +0200 (Mo, 31 Mrz 2008) | 1 line

  Code restructured a bit.
........
  r642975 | jeremias | 2008-03-31 14:24:07 +0200 (Mo, 31 Mrz 2008) | 2 lines

  Plugged fonts package into the event subsystem. Note: I did not follow the same pattern as for the rest as the font package is to be considered FOP-external, so I just added a manual adapter for the FontEventListener. This demonstrates how an external library can be integrated with the event system.
  Missing warning for unknown formatting objects added. Warning is not issued by the ElementMappingRegistry anymore but by FOTreeBuilder which has access to more context information.
........
  r642997 | jeremias | 2008-03-31 16:10:08 +0200 (Mo, 31 Mrz 2008) | 1 line

  Added an example to demonstrate how to write your own event listener and how to deal with the exceptions thrown in the process.
........
  r642998 | jeremias | 2008-03-31 16:13:40 +0200 (Mo, 31 Mrz 2008) | 1 line

  Removed unused method. Event formatting should not be part of the Event class. Use EventFormatter.format(Event) instead.
........
  r643066 | jeremias | 2008-03-31 19:18:54 +0200 (Mo, 31 Mrz 2008) | 1 line

  First part of the event subsystem documentation (DRAFT).
........
  r643784 | jeremias | 2008-04-02 10:05:33 +0200 (Mi, 02 Apr 2008) | 1 line

  More documentation.
........
  r643785 | jeremias | 2008-04-02 10:06:38 +0200 (Mi, 02 Apr 2008) | 1 line

  Some nits.
........
  r643787 | jeremias | 2008-04-02 10:24:41 +0200 (Mi, 02 Apr 2008) | 1 line

  Completed javadocs
........
  r643824 | jeremias | 2008-04-02 12:00:30 +0200 (Mi, 02 Apr 2008) | 1 line

  Javadocs.
........
  r645847 | vhennebert | 2008-04-08 12:54:16 +0200 (Di, 08 Apr 2008) | 2 lines

  Minor typo fixes
........
  r645848 | vhennebert | 2008-04-08 12:58:30 +0200 (Di, 08 Apr 2008) | 2 lines

  Another small typo fix
........
  r647678 | jeremias | 2008-04-14 09:20:26 +0200 (Mo, 14 Apr 2008) | 1 line

  Renamed *EventProducer.Factory.create() to *EventProducer.Provider.get() to better reflect what the method does (instances may be cached and reused).
........

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@647742 13f79535-47bb-0310-9956-ffa450edef68

220 files changed:
build.xml
examples/embedding/java/embedding/events/ExampleEvents.java [new file with mode: 0644]
examples/embedding/java/embedding/events/missing-image.fo [new file with mode: 0644]
lib/build/qdox-1.6.3.jar [new file with mode: 0644]
lib/build/qdox.LICENSE.txt [new file with mode: 0644]
src/codegen/java/org/apache/fop/tools/EventConventionException.java [new file with mode: 0644]
src/codegen/java/org/apache/fop/tools/EventProducerCollector.java [new file with mode: 0644]
src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java [new file with mode: 0644]
src/codegen/java/org/apache/fop/tools/merge-translation.xsl [new file with mode: 0644]
src/codegen/java/org/apache/fop/tools/model2translation.xsl [new file with mode: 0644]
src/documentation/content/xdocs/site.xml
src/documentation/content/xdocs/trunk/events.xml [new file with mode: 0644]
src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory [new file with mode: 0644]
src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory [new file with mode: 0644]
src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function [new file with mode: 0644]
src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter [new file with mode: 0644]
src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory [new file with mode: 0644]
src/java/org/apache/fop/apps/FOPException.java
src/java/org/apache/fop/apps/FOUserAgent.java
src/java/org/apache/fop/area/AreaEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/area/AreaTreeHandler.java
src/java/org/apache/fop/area/AreaTreeObject.java
src/java/org/apache/fop/area/AreaTreeParser.java
src/java/org/apache/fop/area/CachedRenderPagesModel.java
src/java/org/apache/fop/area/PageViewport.java
src/java/org/apache/fop/area/RenderPagesModel.java
src/java/org/apache/fop/events/CompositeEventListener.java [new file with mode: 0644]
src/java/org/apache/fop/events/DefaultEventBroadcaster.java [new file with mode: 0644]
src/java/org/apache/fop/events/Event.java [new file with mode: 0644]
src/java/org/apache/fop/events/EventBroadcaster.java [new file with mode: 0644]
src/java/org/apache/fop/events/EventExceptionManager.java [new file with mode: 0644]
src/java/org/apache/fop/events/EventFormatter.java [new file with mode: 0644]
src/java/org/apache/fop/events/EventFormatter.xml [new file with mode: 0644]
src/java/org/apache/fop/events/EventFormatter_de.xml [new file with mode: 0644]
src/java/org/apache/fop/events/EventListener.java [new file with mode: 0644]
src/java/org/apache/fop/events/EventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/events/FOPEventListenerProxy.java [new file with mode: 0644]
src/java/org/apache/fop/events/FOPEventModelFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/LoggingEventListener.java [new file with mode: 0644]
src/java/org/apache/fop/events/PropertyExceptionFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/ResourceEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/ValidationExceptionFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/AbstractEventModelFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventMethodModel.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventModel.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventModelFactory.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventModelParser.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventProducerModel.java [new file with mode: 0644]
src/java/org/apache/fop/events/model/EventSeverity.java [new file with mode: 0644]
src/java/org/apache/fop/fo/ElementMapping.java
src/java/org/apache/fop/fo/ElementMappingRegistry.java
src/java/org/apache/fop/fo/FOElementMapping.java
src/java/org/apache/fop/fo/FOEventHandler.java
src/java/org/apache/fop/fo/FONode.java
src/java/org/apache/fop/fo/FOText.java
src/java/org/apache/fop/fo/FOTreeBuilder.java
src/java/org/apache/fop/fo/FOValidationEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/fo/FObj.java
src/java/org/apache/fop/fo/PropertyList.java
src/java/org/apache/fop/fo/expr/FromParentFunction.java
src/java/org/apache/fop/fo/expr/InheritedPropFunction.java
src/java/org/apache/fop/fo/expr/NearestSpecPropFunction.java
src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java
src/java/org/apache/fop/fo/extensions/destination/Destination.java
src/java/org/apache/fop/fo/flow/AbstractListItemPart.java
src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
src/java/org/apache/fop/fo/flow/BasicLink.java
src/java/org/apache/fop/fo/flow/BidiOverride.java
src/java/org/apache/fop/fo/flow/BlockContainer.java
src/java/org/apache/fop/fo/flow/Character.java
src/java/org/apache/fop/fo/flow/ExternalGraphic.java
src/java/org/apache/fop/fo/flow/Float.java
src/java/org/apache/fop/fo/flow/Footnote.java
src/java/org/apache/fop/fo/flow/FootnoteBody.java
src/java/org/apache/fop/fo/flow/InitialPropertySet.java
src/java/org/apache/fop/fo/flow/Inline.java
src/java/org/apache/fop/fo/flow/InlineContainer.java
src/java/org/apache/fop/fo/flow/InstreamForeignObject.java
src/java/org/apache/fop/fo/flow/ListBlock.java
src/java/org/apache/fop/fo/flow/ListItem.java
src/java/org/apache/fop/fo/flow/Marker.java
src/java/org/apache/fop/fo/flow/MultiCase.java
src/java/org/apache/fop/fo/flow/MultiProperties.java
src/java/org/apache/fop/fo/flow/MultiPropertySet.java
src/java/org/apache/fop/fo/flow/MultiSwitch.java
src/java/org/apache/fop/fo/flow/MultiToggle.java
src/java/org/apache/fop/fo/flow/PageNumber.java
src/java/org/apache/fop/fo/flow/RetrieveMarker.java
src/java/org/apache/fop/fo/flow/Wrapper.java
src/java/org/apache/fop/fo/flow/table/FixedColRowGroupBuilder.java
src/java/org/apache/fop/fo/flow/table/Table.java
src/java/org/apache/fop/fo/flow/table/TableAndCaption.java
src/java/org/apache/fop/fo/flow/table/TableBody.java
src/java/org/apache/fop/fo/flow/table/TableCaption.java
src/java/org/apache/fop/fo/flow/table/TableCell.java
src/java/org/apache/fop/fo/flow/table/TableCellContainer.java
src/java/org/apache/fop/fo/flow/table/TableColumn.java
src/java/org/apache/fop/fo/flow/table/TableEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/fo/flow/table/TableFObj.java
src/java/org/apache/fop/fo/flow/table/TableRow.java
src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java
src/java/org/apache/fop/fo/pagination/ColorProfile.java
src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java
src/java/org/apache/fop/fo/pagination/Declarations.java
src/java/org/apache/fop/fo/pagination/Flow.java
src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java
src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java
src/java/org/apache/fop/fo/pagination/PageSequence.java
src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java
src/java/org/apache/fop/fo/pagination/PageSequenceWrapper.java
src/java/org/apache/fop/fo/pagination/Region.java
src/java/org/apache/fop/fo/pagination/RegionAfter.java
src/java/org/apache/fop/fo/pagination/RegionBA.java
src/java/org/apache/fop/fo/pagination/RegionBefore.java
src/java/org/apache/fop/fo/pagination/RegionBody.java
src/java/org/apache/fop/fo/pagination/RegionEnd.java
src/java/org/apache/fop/fo/pagination/RegionSE.java
src/java/org/apache/fop/fo/pagination/RegionStart.java
src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java
src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java
src/java/org/apache/fop/fo/pagination/Root.java
src/java/org/apache/fop/fo/pagination/SideRegion.java
src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java
src/java/org/apache/fop/fo/pagination/StaticContent.java
src/java/org/apache/fop/fo/pagination/Title.java
src/java/org/apache/fop/fo/pagination/bookmarks/Bookmark.java
src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTitle.java
src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTree.java
src/java/org/apache/fop/fo/properties/XMLLangShorthandParser.java
src/java/org/apache/fop/fonts/FontEventAdapter.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/FontEventListener.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/FontInfo.java
src/java/org/apache/fop/fonts/LazyFont.java
src/java/org/apache/fop/fonts/SingleByteFont.java
src/java/org/apache/fop/fonts/Typeface.java
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java
src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java
src/java/org/apache/fop/layoutmgr/AbstractBreaker.java
src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java
src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java
src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
src/java/org/apache/fop/layoutmgr/LayoutException.java [new file with mode: 0644]
src/java/org/apache/fop/layoutmgr/PageBreaker.java
src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java
src/java/org/apache/fop/layoutmgr/PageProvider.java
src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java
src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java
src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java
src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java
src/java/org/apache/fop/render/AbstractGenericSVGHandler.java
src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
src/java/org/apache/fop/render/AbstractRenderer.java
src/java/org/apache/fop/render/Renderer.java
src/java/org/apache/fop/render/RendererEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/afp/AFPEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/afp/AFPEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/afp/AFPRenderer.java
src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/bitmap/PNGRenderer.java
src/java/org/apache/fop/render/bitmap/TIFFRenderer.java
src/java/org/apache/fop/render/java2d/Java2DRenderer.java
src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java
src/java/org/apache/fop/render/pcl/PCLEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/PCLEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/PCLGenerator.java
src/java/org/apache/fop/render/pcl/PCLRenderer.java
src/java/org/apache/fop/render/pcl/PCLRendererContext.java
src/java/org/apache/fop/render/pdf/PDFEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFRenderer.java
src/java/org/apache/fop/render/pdf/PDFSVGHandler.java
src/java/org/apache/fop/render/ps/PSEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSRenderer.java
src/java/org/apache/fop/render/ps/PSSVGHandler.java
src/java/org/apache/fop/render/ps/ResourceHandler.java
src/java/org/apache/fop/render/ps/extensions/AbstractPSCommentElement.java
src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionElement.java
src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java
src/java/org/apache/fop/render/ps/extensions/PSCommentAfterElement.java
src/java/org/apache/fop/render/ps/extensions/PSCommentBeforeElement.java
src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java
src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java
src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java
src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java
src/java/org/apache/fop/render/rtf/RTFEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/render/rtf/RTFEventProducer.xml [new file with mode: 0644]
src/java/org/apache/fop/render/rtf/RTFHandler.java
src/java/org/apache/fop/render/xml/XMLRenderer.java
src/java/org/apache/fop/svg/SVGEventProducer.java [new file with mode: 0644]
src/java/org/apache/fop/svg/SVGUserAgent.java
src/java/org/apache/fop/svg/SimpleSVGUserAgent.java [new file with mode: 0644]
src/java/org/apache/fop/util/QName.java
src/java/org/apache/fop/util/XMLResourceBundle.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/AdvancedMessageFormat.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/ChoiceFieldPart.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/EqualsFieldPart.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/GlyphNameFieldPart.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/HexFieldPart.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/IfFieldPart.java [new file with mode: 0644]
src/java/org/apache/fop/util/text/LocatorFormatter.java [new file with mode: 0644]
test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory [new file with mode: 0644]
test/java/org/apache/fop/UtilityCodeTestSuite.java
test/java/org/apache/fop/events/BasicEventTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/events/FOPTestEventModelFactory.java [new file with mode: 0644]
test/java/org/apache/fop/events/TestEventProducer.java [new file with mode: 0644]
test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/util/XMLResourceBundleTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/util/XMLResourceBundleTestCase.xml [new file with mode: 0644]
test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml [new file with mode: 0644]
test/java/org/apache/fop/util/invalid-translation-file.xml [new file with mode: 0644]

index 2169003f81342c5ae96564e3004329c8cf4f2003..5a72154f702d5621456ab67a147175cbd7629cf7 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -118,13 +118,15 @@ list of possible build targets.
     </fileset>
   </path>
 
-  <path id="libs-run-classpath">
-    <fileset dir="${basedir}/lib">
-      <include name="*.jar"/>
-    </fileset>
-    <fileset dir="${optional.lib.dir}">
+  <path id="libs-build-tools-classpath">
+    <path refid="libs-build-classpath"/>
+    <fileset dir="${basedir}/lib/build">
       <include name="*.jar"/>
     </fileset>
+  </path>
+  
+  <path id="libs-run-classpath">
+    <path refid="libs-build-classpath"/>
     <fileset dir="${basedir}/build">
       <include name="fop.jar"/>
       <include name="fop-hyph.jar" />
@@ -362,7 +364,7 @@ list of possible build targets.
   <!-- =================================================================== -->
   <!-- Compiles the source directory                                       -->
   <!-- =================================================================== -->
-  <target name="compile-java" depends="init, codegen" description="Compiles the source code">
+  <target name="compile-java" depends="init, codegen">
     <!-- create directories -->
     <mkdir dir="${build.classes.dir}"/>
     <javac destdir="${build.classes.dir}" fork="${javac.fork}" debug="${javac.debug}"
@@ -375,12 +377,118 @@ list of possible build targets.
       <patternset refid="exclude-jai"/>
       <classpath refid="libs-build-classpath"/>
     </javac>
+
+    <mkdir dir="${build.sandbox-classes.dir}"/>
+    <javac destdir="${build.sandbox-classes.dir}" fork="${javac.fork}" debug="${javac.debug}"
+           deprecation="${javac.deprecation}" optimize="${javac.optimize}"
+           source="${javac.source}" target="${javac.target}">
+      <src path="${src.sandbox.dir}"/>
+      <patternset includes="**/*.java"/>
+      <patternset refid="exclude-jai"/>
+      <classpath>
+        <path refid="libs-build-classpath"/>
+        <pathelement location="${build.classes.dir}"/>
+      </classpath>
+    </javac>
+  </target>
+
+  <target name="resourcegen" depends="compile-java">
+    <mkdir dir="${build.codegen-classes.dir}"/>
+    <javac destdir="${build.codegen-classes.dir}" fork="${javac.fork}" debug="${javac.debug}"
+      deprecation="${javac.deprecation}" optimize="${javac.optimize}"
+      source="${javac.source}" target="${javac.target}">
+      <src path="${src.codegen.dir}/java"/>
+      <patternset includes="**/*.java"/>
+      <classpath>
+        <path refid="libs-build-tools-classpath"/>
+        <pathelement location="${build.classes.dir}"/>
+      </classpath>
+    </javac>
+    <copy todir="${build.codegen-classes.dir}">
+      <fileset dir="${src.codegen.dir}/java">
+        <include name="**/*.xsl"/>
+      </fileset>
+    </copy>
+    
+    <taskdef name="eventResourceGenerator"
+      classname="org.apache.fop.tools.EventProducerCollectorTask">
+      <classpath>
+        <path refid="libs-build-tools-classpath"/>
+        <pathelement location="${build.classes.dir}"/>
+        <pathelement location="${build.codegen-classes.dir}"/>
+      </classpath>
+    </taskdef>
+  
+    <eventResourceGenerator
+        modelfile="${build.gensrc.dir}/org/apache/fop/events/event-model.xml"
+        translationfile="${src.java.dir}/org/apache/fop/events/EventFormatter.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="**/*.java"/>
+        <exclude name="org/apache/fop/render/*/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/events/EventFormatter.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/afp/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/afp/AFPEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/afp/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/afp/AFPEventProducer.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/bitmap/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/bitmap/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/pcl/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/pcl/PCLEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/pcl/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/pcl/PCLEventProducer.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/pdf/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/pdf/PDFEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/pdf/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/pdf/PDFEventProducer.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/ps/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/ps/PSEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/ps/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/ps/PSEventProducer.xml" tab="remove" tablength="2"/>
+    <eventResourceGenerator
+      modelfile="${build.gensrc.dir}/org/apache/fop/render/rtf/event-model.xml"
+      translationfile="${src.java.dir}/org/apache/fop/render/rtf/RTFEventProducer.xml">
+      <fileset dir="${src.java.dir}">
+        <include name="org/apache/fop/render/rtf/**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <fixcrlf file="${src.java.dir}/org/apache/fop/render/rtf/RTFEventProducer.xml" tab="remove" tablength="2"/>
+  </target>
+  
+  <target name="compile-copy-resources" depends="resourcegen">
     <copy todir="${build.classes.dir}">
       <fileset dir="${src.java.dir}">
         <include name="META-INF/**"/>
         <include name="**/*.icm"/>
+        <include name="**/*.xml"/>
         <include name="**/*.LICENSE.txt"/>
       </fileset>
+      <fileset dir="${build.gensrc.dir}">
+        <include name="**/*.xml"/>
+      </fileset>
     </copy>
     <mkdir dir="${build.viewer.resources.dir}"/>
     <copy todir="${build.viewer.resources.dir}">
@@ -390,31 +498,22 @@ list of possible build targets.
     <copy todir="${build.viewer.images.dir}">
       <fileset dir="${src.viewer.images.dir}"/>
     </copy>
-
-    <mkdir dir="${build.sandbox-classes.dir}"/>
-    <javac destdir="${build.sandbox-classes.dir}" fork="${javac.fork}" debug="${javac.debug}"
-           deprecation="${javac.deprecation}" optimize="${javac.optimize}"
-           source="${javac.source}" target="${javac.target}">
-      <src path="${src.sandbox.dir}"/>
-      <patternset includes="**/*.java"/>
-      <patternset refid="exclude-jai"/>
-      <classpath>
-        <path refid="libs-build-classpath"/>
-        <pathelement location="${build.classes.dir}"/>
-      </classpath>
-    </javac>
+    
+    <!-- sandbox -->
     <copy todir="${build.sandbox-classes.dir}">
       <fileset dir="${src.sandbox.dir}">
         <include name="META-INF/**"/>
       </fileset>
     </copy>
+    
   </target>
-
+  
+  <target name="compile" depends="compile-java, compile-copy-resources" description="Compiles the source code"/>
 
   <!-- =================================================================== -->
   <!-- compiles hyphenation patterns                                       -->
   <!-- =================================================================== -->
-  <target name="compile-hyphenation" depends="compile-java">
+  <target name="compile-hyphenation" depends="compile">
     <path id="hyph-classpath">
       <path refid="libs-build-classpath"/>
       <pathelement location="${build.classes.dir}"/>
@@ -452,13 +551,13 @@ list of possible build targets.
   <!-- main FOP JARs                                                       -->
   <!-- =================================================================== -->
 
-  <target name="uptodate-jar-main" depends="compile-java">
+  <target name="uptodate-jar-main" depends="compile">
     <uptodate property="jar.main.uptodate" targetfile="${build.dir}/fop.jar">
       <srcfiles dir= "${build.classes.dir}"/>
     </uptodate>
   </target>
 
-  <target name="jar-main" depends="compile-java,uptodate-jar-main" description="Generates the main jar file" unless="jar.main.uptodate">
+  <target name="jar-main" depends="compile,uptodate-jar-main" description="Generates the main jar file" unless="jar.main.uptodate">
     <tstamp>
       <format property="ts" pattern="yyyyMMdd-HHmmss-z"/>
     </tstamp>
@@ -487,13 +586,13 @@ list of possible build targets.
     </jar>
   </target>
 
-  <target name="uptodate-jar-sandbox" depends="compile-java">
+  <target name="uptodate-jar-sandbox" depends="compile">
     <uptodate property="jar.sandbox.uptodate" targetfile="${build.dir}/fop-sandbox.jar">
       <srcfiles dir= "${build.sandbox-classes.dir}"/>
     </uptodate>
   </target>
 
-  <target name="jar-sandbox" depends="compile-java,uptodate-jar-sandbox" description="Generates the sandbox jar file" unless="jar.sandbox.uptodate">
+  <target name="jar-sandbox" depends="compile,uptodate-jar-sandbox" description="Generates the sandbox jar file" unless="jar.sandbox.uptodate">
     <tstamp>
       <format property="ts" pattern="yyyyMMdd-HHmmss-z"/>
     </tstamp>
@@ -608,14 +707,14 @@ list of possible build targets.
     <include name="xmlgraphics-commons*.jar"/>
   </fileset>
   
-  <target name="uptodate-transcoder-pkg" depends="compile-java">
+  <target name="uptodate-transcoder-pkg" depends="compile">
     <uptodate property="transcoder.pkg.uptodate" targetfile="${build.dir}/fop-transcoder.jar">
       <srcfiles refid="transcoder-classes-files"/>
       <srcfiles refid="transcoder-lib-files"/>
     </uptodate>
   </target>
 
-  <target name="transcoder-pkg" depends="uptodate-transcoder-pkg, compile-java" description="Generates the jar for the transcoder package for Batik" unless="transcoder.pkg.uptodate">
+  <target name="transcoder-pkg" depends="uptodate-transcoder-pkg, compile" description="Generates the jar for the transcoder package for Batik" unless="transcoder.pkg.uptodate">
     <echo message="Creating the jar file ${build.dir}/fop-transcoder.jar"/>
 
     <property name="fop-transcoder.name" value="FOP Transcoder Package"/>
@@ -693,6 +792,7 @@ list of possible build targets.
   </target>
   <target name="junit-compile" depends="package, transcoder-pkg, junit-with-xmlunit, junit-without-xmlunit" description="Runs FOP's JUnit tests" if="junit.present">
     <mkdir dir="${build.dir}/test-classes"/>
+    <mkdir dir="${build.dir}/test-gensrc"/>
     <mkdir dir="${junit.reports.dir}"/>
     <javac destdir="${build.dir}/test-classes" fork="${javac.fork}"
            debug="${javac.debug}" deprecation="${javac.deprecation}"
@@ -707,6 +807,20 @@ list of possible build targets.
         </fileset>
       </classpath>
     </javac>
+    <eventResourceGenerator modelfile="${build.dir}/test-gensrc/org/apache/fop/events/test-event-model.xml">
+      <fileset dir="${basedir}/test/java">
+        <include name="**/*.java"/>
+      </fileset>
+    </eventResourceGenerator>
+    <copy todir="${build.dir}/test-classes">
+      <fileset dir="${basedir}/test/java">
+        <include name="META-INF/**"/>
+        <include name="**/*.xml"/>
+      </fileset>
+      <fileset dir="${build.dir}/test-gensrc">
+        <include name="**/*.xml"/>
+      </fileset>
+    </copy>
   </target>
 
   <target name="junit-transcoder" depends="junit-compile" description="Runs FOP's JUnit transcoder tests" if="junit.present">
diff --git a/examples/embedding/java/embedding/events/ExampleEvents.java b/examples/embedding/java/embedding/events/ExampleEvents.java
new file mode 100644 (file)
index 0000000..9c52e4b
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package embedding.events;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.xml.sax.SAXException;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventFormatter;
+import org.apache.fop.events.EventListener;
+import org.apache.fop.events.model.EventSeverity;
+
+/**
+ * This class demonstrates how to register an event listener with FOP so you can customize
+ * FOP's error behaviour.
+ */
+public class ExampleEvents {
+
+    // configure fopFactory as desired
+    private FopFactory fopFactory = FopFactory.newInstance();
+
+    /**
+     * Converts an FO file to a PDF file using FOP
+     * @param fo the FO file
+     * @param pdf the target PDF file
+     * @throws IOException In case of an I/O problem
+     * @throws FOPException In case of a FOP problem
+     * @throws TransformerException In case of a problem with XSLT
+     */
+    public void convertFO2PDF(URL fo, File pdf)
+            throws IOException, FOPException, TransformerException {
+        
+        OutputStream out = null;
+        
+        try {
+            //Create the user agent for this processing run
+            FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
+            
+            //Adding a simple logging listener that writes to stdout and stderr
+            foUserAgent.getEventBroadcaster().addEventListener(new SysOutEventListener());
+            
+            // Add your own event listener
+            foUserAgent.getEventBroadcaster().addEventListener(new MyEventListener());
+            
+            // configure foUserAgent further as desired
+    
+            // Setup output stream.  Note: Using BufferedOutputStream
+            // for performance reasons (helpful with FileOutputStreams).
+            out = new FileOutputStream(pdf);
+            out = new BufferedOutputStream(out);
+
+            // Construct fop with desired output format
+            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
+
+            // Setup JAXP using identity transformer
+            TransformerFactory factory = TransformerFactory.newInstance();
+            Transformer transformer = factory.newTransformer(); // identity transformer
+            
+            // Setup input stream
+            Source src = new StreamSource(fo.toExternalForm());
+
+            // Resulting SAX events (the generated FO) must be piped through to FOP
+            Result res = new SAXResult(fop.getDefaultHandler());
+            
+            // Start XSLT transformation and FOP processing
+            transformer.transform(src, res);
+
+        } finally {
+            IOUtils.closeQuietly(out);
+        }
+    }
+
+    private static class MyEventListener implements EventListener {
+
+        public void processEvent(Event event) {
+            if ("org.apache.fop.events.ResourceEventProducer.imageNotFound"
+                    .equals(event.getEventID())) {
+                
+                //Get the FileNotFoundException that's part of the event's parameters
+                FileNotFoundException fnfe = (FileNotFoundException)event.getParam("fnfe");
+
+                System.out.println("---=== imageNotFound Event for " + event.getParam("uri")
+                        + "!!! ===---");
+                //Stop processing when an image could not be found. Otherwise, FOP would just
+                //continue without the image!
+                
+                System.out.println("Throwing a RuntimeException...");
+                throw new RuntimeException(EventFormatter.format(event), fnfe);
+            } else {
+                //ignore all other events
+            }
+        }
+        
+    }
+    
+    /** A simple event listener that writes the events to stdout and sterr. */
+    private static class SysOutEventListener implements EventListener {
+
+        /** {@inheritDoc} */
+        public void processEvent(Event event) {
+            String msg = EventFormatter.format(event);
+            EventSeverity severity = event.getSeverity();
+            if (severity == EventSeverity.INFO) {
+                System.out.println("[INFO ] " + msg);
+            } else if (severity == EventSeverity.WARN) {
+                System.out.println("[WARN ] " + msg);
+            } else if (severity == EventSeverity.ERROR) {
+                System.err.println("[ERROR] " + msg);
+            } else if (severity == EventSeverity.FATAL) {
+                System.err.println("[FATAL] " + msg);
+            } else {
+                assert false;
+            }
+        }
+    }
+    
+
+    /**
+     * This method extracts the original exception from some exception. The exception
+     * might be nested multiple levels deep.
+     * @param t the Throwable to inspect
+     * @return the original Throwable or the method parameter t if there are no nested Throwables.
+     */
+    private static Throwable getOriginalThrowable(Throwable t) {
+        if (t instanceof SAXException) {
+            SAXException saxe = (SAXException)t;
+            if (saxe.getException() != null) {
+                return getOriginalThrowable(saxe.getException());
+            } else {
+                return saxe;
+            }
+        } else {
+            if (t.getCause() != null) {
+                return getOriginalThrowable(t.getCause());
+            } else {
+                return t;
+            }
+        }
+    }
+
+    /**
+     * Main method.
+     * @param args command-line arguments
+     */
+    public static void main(String[] args) {
+        try {
+            System.out.println("FOP ExampleEvents\n");
+            System.out.println("Preparing...");
+            
+            //Setup directories
+            File baseDir = new File(".");
+            File outDir = new File(baseDir, "out");
+            outDir.mkdirs();
+
+            //Setup input and output files
+            URL fo = ExampleEvents.class.getResource("missing-image.fo");
+            File pdffile = new File(outDir, "out.pdf");
+
+            System.out.println("Input: XSL-FO (" + fo.toExternalForm() + ")");
+            System.out.println("Output: PDF (" + pdffile + ")");
+            System.out.println();
+            System.out.println("Transforming...");
+            
+            ExampleEvents app = new ExampleEvents();
+            
+            try {
+                app.convertFO2PDF(fo, pdffile);
+            } catch (TransformerException te) {
+                //Note: We don't get the original exception here!
+                //FOP needs to embed the exception in a SAXException and the TraX transformer
+                //again wraps the SAXException in a TransformerException. Even our own
+                //RuntimeException just wraps the original FileNotFoundException.
+                //So we need to unpack to get the original exception (about three layers deep).
+                Throwable originalThrowable = getOriginalThrowable(te);
+                originalThrowable.printStackTrace(System.err);
+                System.out.println("Aborted!");
+                System.exit(-1);
+            }
+            
+            System.out.println("Success!");
+        } catch (Exception e) {
+            //Some other error (shouldn't happen in this example)
+            e.printStackTrace(System.err);
+            System.exit(-1);
+        }
+    }
+
+}
diff --git a/examples/embedding/java/embedding/events/missing-image.fo b/examples/embedding/java/embedding/events/missing-image.fo
new file mode 100644 (file)
index 0000000..2c55f66
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
+  <fo:layout-master-set>
+    <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm">
+      <fo:region-body/>
+    </fo:simple-page-master>
+  </fo:layout-master-set>
+  <fo:page-sequence master-reference="A4">
+    <fo:flow flow-name="xsl-region-body">
+      <fo:block>
+        The following image is not available:
+        <fo:external-graphic src="my-missing-image.png"/>
+      </fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+</fo:root>
diff --git a/lib/build/qdox-1.6.3.jar b/lib/build/qdox-1.6.3.jar
new file mode 100644 (file)
index 0000000..3e99cb0
Binary files /dev/null and b/lib/build/qdox-1.6.3.jar differ
diff --git a/lib/build/qdox.LICENSE.txt b/lib/build/qdox.LICENSE.txt
new file mode 100644 (file)
index 0000000..3e4e3d0
--- /dev/null
@@ -0,0 +1,201 @@
+                                  Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/src/codegen/java/org/apache/fop/tools/EventConventionException.java b/src/codegen/java/org/apache/fop/tools/EventConventionException.java
new file mode 100644 (file)
index 0000000..675f4a0
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.tools;
+
+public class EventConventionException extends Exception {
+
+    public EventConventionException(String message) {
+        super(message);
+    }
+    
+}
diff --git a/src/codegen/java/org/apache/fop/tools/EventProducerCollector.java b/src/codegen/java/org/apache/fop/tools/EventProducerCollector.java
new file mode 100644 (file)
index 0000000..e42395a
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.tools;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.EventMethodModel;
+import org.apache.fop.events.model.EventModel;
+import org.apache.fop.events.model.EventProducerModel;
+import org.apache.fop.events.model.EventSeverity;
+
+import com.thoughtworks.qdox.JavaDocBuilder;
+import com.thoughtworks.qdox.model.DefaultDocletTagFactory;
+import com.thoughtworks.qdox.model.DocletTag;
+import com.thoughtworks.qdox.model.DocletTagFactory;
+import com.thoughtworks.qdox.model.JavaClass;
+import com.thoughtworks.qdox.model.JavaMethod;
+import com.thoughtworks.qdox.model.JavaParameter;
+import com.thoughtworks.qdox.model.Type;
+
+/**
+ * Finds EventProducer interfaces and builds the event model for them.
+ */
+public class EventProducerCollector {
+
+    private static final String CLASSNAME_EVENT_PRODUCER = EventProducer.class.getName();
+    private static final Map PRIMITIVE_MAP;
+    
+    static {
+        Map m = new java.util.HashMap();
+        m.put("boolean", Boolean.class);
+        m.put("byte", Byte.class);
+        m.put("char", Character.class);
+        m.put("short", Short.class);
+        m.put("int", Integer.class);
+        m.put("long", Long.class);
+        m.put("float", Float.class);
+        m.put("double", Double.class);
+        PRIMITIVE_MAP = Collections.unmodifiableMap(m);
+    }
+    
+    private DocletTagFactory tagFactory;
+    private EventModel model = new EventModel();
+
+    public EventProducerCollector() {
+        this.tagFactory = createDocletTagFactory();
+    }
+
+    protected DocletTagFactory createDocletTagFactory() {
+        return new DefaultDocletTagFactory();
+    }
+
+    public void scanFile(File src, String filename)
+            throws IOException, EventConventionException, ClassNotFoundException {
+        JavaDocBuilder builder = new JavaDocBuilder(this.tagFactory);
+        builder.addSource(src);
+        JavaClass[] classes = builder.getClasses();
+        for (int i = 0, c = classes.length; i < c; i++) {
+            JavaClass clazz = classes[i];
+            if (clazz.isInterface() && implementsInterface(clazz, CLASSNAME_EVENT_PRODUCER)) {
+                processEventProducerInterface(clazz, filename);
+            }
+        }
+    }
+
+    private boolean implementsInterface(JavaClass clazz, String intf) {
+        JavaClass[] classes = clazz.getImplementedInterfaces();
+        for (int i = 0, c = classes.length; i < c; i++) {
+            JavaClass cl = classes[i];
+            if (cl.getFullyQualifiedName().equals(intf)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Processes an EventProducer interface and creates an EventProducerModel from it.
+     * @param clazz the EventProducer interface
+     * @param javaFilename the filename of the Java source of the interface
+     * @throws EventConventionException if the event producer conventions are violated
+     * @throws ClassNotFoundException if a required class cannot be found
+     */
+    protected void processEventProducerInterface(JavaClass clazz, String javaFilename)
+                throws EventConventionException, ClassNotFoundException {
+        EventProducerModel prodMeta = new EventProducerModel(clazz.getFullyQualifiedName());
+        JavaMethod[] methods = clazz.getMethods(true);
+        for (int i = 0, c = methods.length; i < c; i++) {
+            JavaMethod method = methods[i];
+            EventMethodModel methodMeta = createMethodModel(method);
+            prodMeta.addMethod(methodMeta);
+        }
+        this.model.addProducer(prodMeta);
+    }
+
+    private EventMethodModel createMethodModel(JavaMethod method)
+            throws EventConventionException, ClassNotFoundException {
+        JavaClass clazz = method.getParentClass();
+        //Check EventProducer conventions
+        if (!method.getReturns().isVoid()) {
+            throw new EventConventionException("All methods of interface "
+                    + clazz.getFullyQualifiedName() + " must have return type 'void'!");
+        }
+        String methodSig = clazz.getFullyQualifiedName() + "." + method.getCallSignature();
+        JavaParameter[] params = method.getParameters();
+        if (params.length < 1) {
+            throw new EventConventionException("The method " + methodSig
+                    + " must have at least one parameter: 'Object source'!");
+        }
+        Type firstType = params[0].getType();
+        if (firstType.isPrimitive() || !"source".equals(params[0].getName())) {
+            throw new EventConventionException("The first parameter of the method " + methodSig
+                    + " must be: 'Object source'!");
+        }
+        
+        //build method model
+        DocletTag tag = method.getTagByName("event.severity");
+        EventSeverity severity;
+        if (tag != null) {
+            severity = EventSeverity.valueOf(tag.getValue());
+        } else { 
+            severity = EventSeverity.INFO;
+        }
+        EventMethodModel methodMeta = new EventMethodModel(
+                method.getName(), severity);
+        if (params.length > 1) {
+            for (int j = 1, cj = params.length; j < cj; j++) {
+                JavaParameter p = params[j];
+                Class type;
+                JavaClass pClass = p.getType().getJavaClass();
+                if (p.getType().isPrimitive()) {
+                    type = (Class)PRIMITIVE_MAP.get(pClass.getName());
+                    if (type == null) {
+                        throw new UnsupportedOperationException(
+                                "Primitive datatype not supported: " + pClass.getName());
+                    }
+                } else {
+                    String className = pClass.getFullyQualifiedName();
+                    type = Class.forName(className);
+                }
+                methodMeta.addParameter(type, p.getName());
+            }
+        }
+        Type[] exceptions = method.getExceptions();
+        if (exceptions != null && exceptions.length > 0) {
+            //We only use the first declared exception because that is always thrown
+            JavaClass cl = exceptions[0].getJavaClass();
+            methodMeta.setExceptionClass(cl.getFullyQualifiedName());
+            methodMeta.setSeverity(EventSeverity.FATAL); //In case it's not set in the comments
+        }
+        return methodMeta;
+    }
+
+    public EventModel getModel() {
+        return this.model;
+    }
+    
+    public void saveModelToXML(File modelFile) throws IOException {
+        getModel().saveToXML(modelFile);
+    }
+    
+}
diff --git a/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java b/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java
new file mode 100644 (file)
index 0000000..755e3da
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.tools;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Node;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.selectors.FilenameSelector;
+
+public class EventProducerCollectorTask extends Task {
+
+    private List filesets = new java.util.ArrayList();
+    private File modelFile;
+    private File translationFile;
+    
+    /** {@inheritDoc} */
+    public void execute() throws BuildException {
+        try {
+            EventProducerCollector collector = new EventProducerCollector();
+            processFileSets(collector);
+            getModelFile().getParentFile().mkdirs();
+            collector.saveModelToXML(getModelFile());
+            log("Event model written to " + getModelFile());
+            if (getTranslationFile() != null) {
+                updateTranslationFile();
+            }
+        } catch (ClassNotFoundException e) {
+            throw new BuildException(e);
+        } catch (EventConventionException ece) {
+            throw new BuildException(ece);
+        } catch (IOException ioe) {
+            throw new BuildException(ioe);
+        }
+    }
+    
+    private static final String MODEL2TRANSLATION = "model2translation.xsl";
+    private static final String MERGETRANSLATION = "merge-translation.xsl";
+    
+    protected void updateTranslationFile() throws IOException {
+        try {
+            boolean resultExists = getTranslationFile().exists();
+            SAXTransformerFactory tFactory
+                = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+            
+            //Generate fresh generated translation file as template
+            Source src = new StreamSource(getModelFile());
+            StreamSource xslt1 = new StreamSource(
+                    getClass().getResourceAsStream(MODEL2TRANSLATION));
+            if (xslt1.getInputStream() == null) {
+                throw new FileNotFoundException(MODEL2TRANSLATION + " not found");
+            }
+            DOMResult domres = new DOMResult();
+            Transformer transformer = tFactory.newTransformer(xslt1);
+            transformer.transform(src, domres);
+            final Node generated = domres.getNode();
+            
+            Node sourceDocument;
+            if (resultExists) {
+                //Load existing translation file into memory (because we overwrite it later)
+                src = new StreamSource(getTranslationFile());
+                domres = new DOMResult();
+                transformer = tFactory.newTransformer();
+                transformer.transform(src, domres);
+                sourceDocument = domres.getNode();
+            } else {
+                //Simply use generated as source document
+                sourceDocument = generated;
+            }
+
+            //Generate translation file (with potentially new translations)
+            src = new DOMSource(sourceDocument);
+            Result res = new StreamResult(getTranslationFile());
+            StreamSource xslt2 = new StreamSource(
+                    getClass().getResourceAsStream(MERGETRANSLATION));
+            if (xslt2.getInputStream() == null) {
+                throw new FileNotFoundException(MERGETRANSLATION + " not found");
+            }
+            transformer = tFactory.newTransformer(xslt2);
+            transformer.setURIResolver(new URIResolver() {
+                public Source resolve(String href, String base) throws TransformerException {
+                    if ("my:dom".equals(href)) {
+                        return new DOMSource(generated);
+                    }
+                    return null;
+                }
+            });
+            if (resultExists) {
+                transformer.setParameter("generated-url", "my:dom");
+            }
+            transformer.transform(src, res);
+            if (resultExists) {
+                log("Translation file updated: " + getTranslationFile());
+            } else {
+                log("Translation file generated: " + getTranslationFile());
+            }
+        } catch (TransformerException te) {
+            throw new IOException(te.getMessage());
+        }
+    }
+
+    protected void processFileSets(EventProducerCollector collector)
+            throws IOException, EventConventionException, ClassNotFoundException {
+        Iterator iter = filesets.iterator();
+        while (iter.hasNext()) {
+            FileSet fs = (FileSet)iter.next();
+            DirectoryScanner ds = fs.getDirectoryScanner(getProject());
+            String[] srcFiles = ds.getIncludedFiles();
+            File directory = fs.getDir(getProject());
+            for (int i = 0, c = srcFiles.length; i < c; i++) {
+                String filename = srcFiles[i];
+                File src = new File(directory, filename);
+                collector.scanFile(src, filename);
+            }
+        }
+    }
+
+    public void addFileset(FileSet set) {
+        filesets.add(set);
+    }
+    
+    public void setModelFile(File f) {
+        this.modelFile = f;
+    }
+    
+    public File getModelFile() {
+        return this.modelFile;
+    }
+    
+    public void setTranslationFile(File f) {
+        this.translationFile = f;
+    }
+    
+    public File getTranslationFile() {
+        return this.translationFile;
+    }
+    
+    public static void main(String[] args) {
+        try {
+            Project project = new Project();
+
+            EventProducerCollectorTask generator = new EventProducerCollectorTask();
+            generator.setProject(project);
+            project.setName("Test");
+            FileSet fileset = new FileSet();
+            fileset.setDir(new File("test/java"));
+            
+            FilenameSelector selector = new FilenameSelector();
+            selector.setName("**/*.java");
+            fileset.add(selector);
+            generator.addFileset(fileset);
+            
+            File targetDir = new File("build/codegen1");
+            targetDir.mkdirs();
+            
+            generator.setModelFile(new File("D:/out.xml"));
+            generator.setTranslationFile(new File("D:/out1.xml"));
+            generator.execute();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/codegen/java/org/apache/fop/tools/merge-translation.xsl b/src/codegen/java/org/apache/fop/tools/merge-translation.xsl
new file mode 100644 (file)
index 0000000..d15d22f
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+  
+  http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+  <xsl:output indent="yes" method="xml" encoding="UTF-8"/>
+
+  <xsl:param name="generated-url" select="''"/>
+  
+  <xsl:template match="catalogue">
+    <catalogue>
+      <xsl:attribute name="xml:lang"><xsl:value-of select="@xml:lang"/></xsl:attribute>
+      <xsl:apply-templates/>
+      <xsl:if test="$generated-url != ''">
+        <xsl:variable name="generated" select="document($generated-url)"/>
+        <xsl:call-template name="add-new-messages">
+          <xsl:with-param name="existing" select="."/>
+          <xsl:with-param name="new" select="$generated/catalogue"/>
+        </xsl:call-template>
+      </xsl:if>
+    </catalogue>
+  </xsl:template>
+  
+  <xsl:template name="add-new-messages">
+    <xsl:param name="existing"/>
+    <xsl:param name="new"/>
+    <xsl:for-each select="$new/message">
+      <xsl:variable name="k" select="@key"/>
+      <xsl:if test="not(boolean($existing/message[@key = $k]))">
+        <xsl:apply-templates select="."></xsl:apply-templates>
+      </xsl:if>
+    </xsl:for-each>
+  </xsl:template>  
+  
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/codegen/java/org/apache/fop/tools/model2translation.xsl b/src/codegen/java/org/apache/fop/tools/model2translation.xsl
new file mode 100644 (file)
index 0000000..a1cf404
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+  
+  http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+  <xsl:output indent="yes" method="xml" encoding="UTF-8"/>
+
+  <xsl:template match="event-model">
+    <catalogue>
+      <xsl:attribute name="xml:lang">en</xsl:attribute>
+      <xsl:apply-templates select="//method"></xsl:apply-templates>
+    </catalogue>
+  </xsl:template>
+  
+  <xsl:template match="method">
+    <message>
+      <xsl:attribute name="key"><xsl:value-of select="../@name"/>.<xsl:value-of select="@name"/></xsl:attribute>
+    </message>
+  </xsl:template>
+  
+</xsl:stylesheet>
index 18716903c33f7c61c6dede520b718b511f9994db..b0a3214e7892b1ce9f3e9b05096132ccad18b37c 100644 (file)
       <fonts label="Fonts" href="fonts.html"/>
       <hyphenation label="Hyphenation" href="hyphenation.html"/>    
       <extensions label="Extensions" href="extensions.html"/>
+      <events label="Events" href="events.html"/>
     </features>
     
   </trunk>
diff --git a/src/documentation/content/xdocs/trunk/events.xml b/src/documentation/content/xdocs/trunk/events.xml
new file mode 100644 (file)
index 0000000..d2fe531
--- /dev/null
@@ -0,0 +1,422 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+  <header>
+    <title>Events/Processing Feedback</title>
+    <version>$Revision: 634267 $</version>
+  </header>
+  <body>
+    <section id="introduction">
+      <title>Introduction</title>
+      <p>
+        In versions until 0.20.5, FOP used
+        <a href="http://excalibur.apache.org/framework/index.html">Avalon-style Logging</a> where
+        it was possible to supply a logger per processing run. During the redesign
+        the logging infrastructure was switched over to
+        <a href="http://commons.apache.org/logging/">Commons Logging</a> which is (like Log4J or
+        java.util.logging) a "static" logging framework (the logger is accessed through static
+        variables). This made it very difficult in a multi-threaded system to retrieve information
+        for a single processing run.
+      </p>
+      <p>
+        With FOP's event subsystem, we'd like to close this gap again and even go further. The
+        first point is to realize that we have two kinds of "logging". Firstly, we have the logging
+        infrastructure for the (FOP) developer who needs to be able to enable finer log messages
+        for certain parts of FOP to track down a certain problem. Secondly, we have the user who
+        would like to be informed about missing images, overflowing lines or substituted fonts.
+        These messages (or events) are targeted at less technical people and may ideally be
+        localized (translated). Furthermore, tool and solution builders would like to integrate
+        FOP into their own solutions. For example, an FO editor should be able to point the user
+        to the right place where a particular problem occurred while developing a document template.
+        Finally, some integrators would like to abort processing if a resource (an image or a font)
+        has not been found, while others would simply continue. The event system allows to
+        react on these events.
+      </p>
+      <p>
+        On this page, we won't discuss logging as such. We will show how the event subsystem can
+        be used for various tasks. We'll first look at the event subsystem from the consumer side.
+        Finally, the production of events inside FOP will be discussed (this is mostly interesting
+        for FOP developers only).
+      </p>
+    </section>
+    <section id="consumer">
+      <title>The consumer side</title>
+      <p>
+        The event subsystem is located in the <code>org.apache.fop.events</code> package and its
+        base is the <code>Event</code> class. An instance is created for each event and is sent
+        to a set of <code>EventListener</code> instances by the <code>EventBroadcaster</code>.
+        An <code>Event</code> contains:
+      </p>
+      <ul>
+        <li>an event ID,</li>
+        <li>a source object (which generated the event),</li>
+        <li>a severity level (Info, Warning, Error and Fatal Error) and</li>
+        <li>a map of named parameters.</li>
+      </ul>
+      <p>
+        The <code>EventFormatter</code> class can be used to translate the events into
+        human-readable, localized messages.
+      </p>
+      <p>
+        A full example of what is shown here can be found in the
+        <code>examples/embedding/java/embedding/events</code> directory in the FOP distribution.
+        The example can also be accessed
+        <a href="http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/examples/embedding/java/embedding/events/">via the web</a>.
+      </p>
+      <section id="write-listener">
+        <title>Writing an EventListener</title>
+        <p>
+          The following code sample shows a very simple EventListener. It basically just sends
+          all events to System.out (stdout) or System.err (stderr) depending on the event severity.
+        </p>
+        <source><![CDATA[import org.apache.fop.events.Event;
+import org.apache.fop.events.EventFormatter;
+import org.apache.fop.events.EventListener;
+import org.apache.fop.events.model.EventSeverity;
+
+/** A simple event listener that writes the events to stdout and stderr. */
+public class SysOutEventListener implements EventListener {
+
+    /** {@inheritDoc} */
+    public void processEvent(Event event) {
+        String msg = EventFormatter.format(event);
+        EventSeverity severity = event.getSeverity();
+        if (severity == EventSeverity.INFO) {
+            System.out.println("[INFO ] " + msg);
+        } else if (severity == EventSeverity.WARN) {
+            System.out.println("[WARN ] " + msg);
+        } else if (severity == EventSeverity.ERROR) {
+            System.err.println("[ERROR] " + msg);
+        } else if (severity == EventSeverity.FATAL) {
+            System.err.println("[FATAL] " + msg);
+        } else {
+            assert false;
+        }
+    }
+}]]></source>
+        <p>
+          You can see that for every event the method <code>processEvent</code> of the
+          <code>EventListener</code> will be called. Inside this method you can do whatever
+          processing you would like including throwing a <code>RuntimeException</code>, if you want
+          to abort the current processing run.
+        </p>
+        <p>
+          The code above also shows how you can turn an event into a human-readable, localized
+          message that can be presented to a user. The <code>EventFormatter</code> class does
+          this for you. It provides additional methods if you'd like to explicitly specify
+          the locale.
+        </p>
+        <p>
+          It is possible to gather all events for a whole processing run so they can be
+          evaluated afterwards. However, care should be taken about memory consumption since
+          the events provide references to objects inside FOP which may themselves have
+          references to other objects. So holding on to these objects may mean that whole
+          object trees cannot be released!
+        </p>
+      </section>
+      <section id="add-listener">
+        <title>Adding an EventListener</title>
+        <p>
+          To register the event listener with FOP, get the <code>EventBroadcaster</code> which
+          is associated with the user agent (<code>FOUserAgent</code>) and add it there:
+        </p>
+        <source><![CDATA[FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
+foUserAgent.getEventBroadcaster().addEventListener(new SysOutEventListener());]]></source>
+        <p>
+          Please note that this is done separately for each processing run, i.e. for each
+          new user agent.
+        </p>
+      </section>
+      <section id="listener-example1">
+        <title>An additional listener example</title>
+        <p>
+          Here's an additional example of an event listener:
+        </p>
+        <p>
+          By default, FOP continues processing even if an image wasn't found. If you have
+          more strict requirements and want FOP to stop if an image is not available, you can
+          do something like the following:
+        </p>
+        <source><![CDATA[public class MyEventListener implements EventListener {
+
+    public void processEvent(Event event) {
+        if ("org.apache.fop.events.ResourceEventProducer.imageNotFound"
+                .equals(event.getEventID())) {
+            
+            //Get the FileNotFoundException that's part of the event's parameters
+            FileNotFoundException fnfe = (FileNotFoundException)event.getParam("fnfe");
+
+            throw new RuntimeException(EventFormatter.format(event), fnfe);
+        } else {
+            //ignore all other events (or do something of your choice)
+        }
+    }
+    
+}]]></source>
+        <p>
+          This throws a <code>RuntimeException</code> with the <code>FileNotFoundException</code>
+          as the cause. Further processing effectively stops in FOP. You can catch the exception
+          in your code and react as you see necessary.
+        </p>
+      </section>
+    </section>
+    <section id="producer">
+      <title>The producer side (for FOP developers)</title>
+      <p>
+        This section is primarily for FOP and FOP plug-in developers. It describes how to use
+        the event subsystem for producing events.
+      </p>
+      <note>
+        The event package has been designed in order to be theoretically useful for use cases
+        outside FOP. If you think this is interesting independently from FOP, please talk to
+        <a href="mailto:fop-dev@xmlgraphics.apache.org">us</a>.
+      </note>
+      <section id="basic-event-production">
+        <title>Producing and sending an event</title>
+        <p>
+          The basics are very simple. Just instantiate an <code>Event</code> object and fill
+          it with the necessary parameters. Then pass it to the <code>EventBroadcaster</code>
+          which distributes the events to the interested listeneners. Here's a code example:
+        </p>
+        <source><![CDATA[Event ev = new Event(this, "complain", EventSeverity.WARN,
+        Event.paramsBuilder()
+            .param("reason", "I'm tired")
+            .param("blah", new Integer(23))
+            .build());
+EventBroadcaster broadcaster = [get it from somewhere];
+broadcaster.broadcastEvent(ev);
+]]></source>
+        <p>
+          The <code>Event.paramsBuilder()</code> is a
+          <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>
+          to help with the build-up of the parameters. You could just as well instantiate a
+          <code>Map</code> (<code>Map&lt;String, Object&gt;</code>) and fill it with values.
+        </p>
+      </section>
+      <section id="event-producer">
+        <title>The EventProducer interface</title>
+        <p>
+          To simplify event production, the event subsystem provides the <code>EventProducer</code>
+          interface. You can create interfaces which extend <code>EventProducer</code>. These
+          interfaces will contain one method per event to be generated. By contract, each event
+          method must have as its first parameter a parameter named "source" (Type Object) which
+          indicates the object that generated the event. After that come an arbitrary number of
+          parameters of any type as needed by the event.
+        </p>
+        <p>
+          The event producer interface does not need to have any implementation. The implementation
+          is produced at runtime by a dynamic proxy created by <code>DefaultEventBroadcaster</code>.
+          The dynamic proxy creates <code>Event</code> instances for each method call against
+          the event producer interface. Each parameter (except "source") is added to the event's
+          parameter map.
+        </p>
+        <p>
+          To simplify the code needed to get an instance of the event producer interface it is
+          suggested to create a public inner provider class inside the interface.
+        </p>
+        <p>
+          Here's an example of such an event producer interface:
+        </p>
+        <source><![CDATA[public interface MyEventProducer extends EventProducer {
+
+    public class Provider {
+        
+        public static MyEventProducer get(EventBroadcaster broadcaster) {
+            return (MyEventProducer)broadcaster.getEventProducerFor(MyEventProducer.class);
+        }
+    }
+
+    /**
+     * Complain about something.
+     * @param source the event source
+     * @param reason the reason for the complaint
+     * @param blah the complaint
+     * @event.severity WARN
+     */
+    void complain(Object source, String reason, int blah);
+    
+}]]></source>
+        <p>
+          To produce the same event as in the first example above, you'd use the following code:
+        </p>
+        <source><![CDATA[EventBroadcaster broadcaster = [get it from somewhere];
+TestEventProducer producer = TestEventProducer.Provider.get(broadcaster);
+producer.complain(this, "I'm tired", 23);]]></source>
+      </section>
+      <section id="event-model">
+        <title>The event model</title>
+        <p>
+          Inside an invocation handler for a dynamic proxy, there's no information about
+          the names of each parameter. The JVM doesn't provide it. The only thing you know is
+          the interface and method name. In order to properly fill the <code>Event</code>'s
+          parameter map we need to know the parameter names. These are retrieved from an
+          event object model. This is found in the <code>org.apache.fop.events.model</code>
+          package. The data for the object model is retrieved from an XML representation of the
+          event model that is loaded as a resource. The XML representation is generated using an
+          Ant task at build time (<code>ant resourcegen</code>). The Ant task (found in
+          <code>src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java</code>)
+          scans FOP's sources for descendants of the <code>EventProducer</code> interface and
+          uses <a href="http://qdox.codehaus.org/">QDox</a> to parse these interfaces.
+        </p>
+        <p>
+          Primarily, the QDox-based collector task records the parameters' names and types.
+          Furthermore, it extracts additional attributes embedded as Javadoc comments from
+          the methods. At the moment, the only such attribute is "@event.severity" which indicates
+          the default event severity (which can be changed by event listeners). The example event
+          producer above shows the Javadocs for an event method.
+        </p>
+        <p>
+          There's one more information that is extracted from the event producer information for
+          the event model: an optional primary exception. The first exception in the "throws"
+          declaration of an event method is noted. It is used to throw an exception from
+          the invocation handler if the event has an event severity of "FATAL" when all
+          listeners have been called (listeners can update the event severity). Please note
+          that an implementation of
+          <code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code> has to be
+          registered for the <code>EventExceptionManager</code> to be able to construct the
+          exception from an event.
+        </p>
+        <p>
+          For a given application, there can be multiple event models active at the same time.
+          In FOP, each renderer is considered to be a plug-in and provides its own specific
+          event model. The individual event models are provided through an
+          <code>EventModelFactory</code>. This interface is implemented for each event model
+          and registered through the service provider mechanism
+          (see the <a href="#plug-ins">plug-ins section</a> for details).
+        </p>
+      </section>
+      <section id="event-severity">
+        <title>Event severity</title>
+        <p>
+          Four different levels of severity for events has been defined:
+        </p>
+        <ol>
+          <li>INFO: informational only</li>
+          <li>WARN: a Warning</li>
+          <li>ERROR: an error condition from which FOP can recover. FOP will continue processing.</li>
+          <li>FATAL: a fatal error which causes an exception in the end and FOP will stop processing.</li>
+        </ol>
+        <p>
+          Event listeners can choose to ignore certain events based on their event severity.
+          Please note that you may recieve an event "twice" in a specific case: if there is
+          a fatal error an event is generated and sent to the listeners. After that an exception
+          is thrown with the same information and processing stops. If the fatal event is
+          shown to the user and the following exception is equally presented to the user it
+          may appear that the event is duplicated. Of course, the same information is just
+          published through two different channels.
+        </p>
+      </section>
+      <section id="plug-ins">
+        <title>Plug-ins to the event subsystem</title>
+        <p>
+          The event subsystem is extensible. There are a number of extension points:
+        </p>
+        <ul>
+          <li>
+            <strong><code>org.apache.fop.events.model.EventModelFactory</code>:</strong> Provides
+            an event model to the event subsystem.
+          </li>
+          <li>
+            <strong><code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code>:</strong>
+            Creates exceptions for events, i.e. turns an event into a specific exception.
+          </li>
+        </ul>
+        <p>
+          The names in bold above are used as filenames for the service provider files that
+          are placed in the <code>META-INF/services</code> directory. That way, they are
+          automatically detected. This is a mechanism defined by the
+          <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html#Service%20Provider">JAR file specification</a>.
+        </p>
+      </section>
+      <section id="l10n">
+        <title>Localization (L10n)</title>
+        <p>
+          One goal of the event subsystem was to have localized (translated) event messages.
+          The <code>EventFormatter</code> class can be used to convert an event to a
+          human-readable message. Each <code>EventProducer</code> can provide its own XML-based
+          translation file. If there is none, a central translation file is used, called
+          "EventFormatter.xml" (found in the same directory as the <code>EventFormatter</code>
+          class).
+        </p>
+        <p>
+          The XML format used by the <code>EventFormatter</code> is the same as
+          <a href="ext:cocoon">Apache Cocoon's</a> catalog format. Here's an example:
+        </p>
+        <source><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="locator">
+    [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]
+  </message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.explicitTableColumnsRequired">
+    RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}}
+  </message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.ignoredDeferredEvent">
+    Ignored deferred event for {node} ({start,if,start,end}).{{locator}}
+  </message>
+</catalogue>
+]]></source>
+        <p>
+          The example (extracted from the RTF handler's event producer) has message templates for
+          two event methods. The class used to do variable replacement in the templates is
+          <code>org.apache.fop.util.text.AdvancedMessageFormat</code> which is more powerful
+          than the <code>MessageFormat</code> classes provided by the Java class library
+          (<code>java.util.text</code> package).
+        </p>
+        <p>
+          "locator" is a template that is reused by the other message templates
+          by referencing it through "{{locator}}". This is some kind of include command.
+        </p>
+        <p>
+          Normal event parameters are accessed by name inside single curly braces, for example:
+          "{node}". For objects, this format just uses the <code>toString()</code> method to turn
+          the object into a string, unless there is an <code>ObjectFormatter</code> registered
+          for that type (there's an example for <code>org.xml.sax.Locator</code>).
+        </p>
+        <p>
+          The single curly braces pattern supports additional features. For example, it is possible
+          to do this: "{start,if,start,end}". "if" here is a special field modifier that evaluates
+          "start" as a boolean and if that is true returns the text right after the second comma
+          ("start"). Otherwise it returns the text after the third comma ("end"). The "equals"
+          modifier is similar to "if" but it takes as an additional (comma-separated) parameter
+          right after the "equals" modifier, a string that is compared to the value of the variable.
+          An example: {severity,equals,EventSeverity:FATAL,,some text} (this adds "some text" if
+          the severity is not FATAL).
+        </p>
+        <p>
+          Additional such modifiers can be added by implementing the
+          <code>AdvancedMessageFormat$Part</code> and <code>AdvancedMessageFormat$PartFactory</code>
+          interfaces.
+        </p>
+        <p>
+          Square braces can be used to specify optional template sections. The whole section will
+          be omitted if any of the variables used within are unavailable. Pipe (|) characters can
+          be used to specify alternative sub-templates (see "locator" above for an example).
+        </p>
+        <p>
+          Developers can also register a function (in the above example:
+          <code>{#gatherContextInfo})</code>
+          to do more complex information rendering. These functions are implementations of the
+          <code>AdvancedMessageFormat$Function</code> interface. Please take care that this is
+          done in a locale-independent way as there is no locale information available, yet.
+        </p>
+      </section>
+    </section>
+  </body>
+</document>
diff --git a/src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory b/src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory
new file mode 100644 (file)
index 0000000..9fa7b8d
--- /dev/null
@@ -0,0 +1,4 @@
+org.apache.fop.events.ValidationExceptionFactory\r
+org.apache.fop.events.PropertyExceptionFactory\r
+org.apache.fop.events.UnsupportedOperationExceptionFactory\r
+org.apache.fop.layoutmgr.LayoutException$LayoutExceptionFactory\r
diff --git a/src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory b/src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory
new file mode 100644 (file)
index 0000000..8dc13db
--- /dev/null
@@ -0,0 +1,7 @@
+org.apache.fop.events.FOPEventModelFactory\r
+org.apache.fop.render.afp.AFPEventProducer$EventModelFactory\r
+org.apache.fop.render.bitmap.BitmapRendererEventProducer$EventModelFactory\r
+org.apache.fop.render.pcl.PCLEventProducer$EventModelFactory\r
+org.apache.fop.render.pdf.PDFEventProducer$EventModelFactory\r
+org.apache.fop.render.ps.PSEventProducer$EventModelFactory\r
+org.apache.fop.render.rtf.RTFEventProducer$EventModelFactory\r
diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function
new file mode 100644 (file)
index 0000000..375130f
--- /dev/null
@@ -0,0 +1 @@
+org.apache.fop.fo.FONode$GatherContextInfoFunction
diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter
new file mode 100644 (file)
index 0000000..9e3860b
--- /dev/null
@@ -0,0 +1 @@
+org.apache.fop.util.text.LocatorFormatter
diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory
new file mode 100644 (file)
index 0000000..1647fb8
--- /dev/null
@@ -0,0 +1,6 @@
+org.apache.fop.util.text.IfFieldPart$Factory
+org.apache.fop.util.text.EqualsFieldPart$Factory
+org.apache.fop.util.text.ChoiceFieldPart$Factory
+org.apache.fop.util.text.HexFieldPart$Factory
+org.apache.fop.util.text.GlyphNameFieldPart$Factory
+org.apache.fop.events.EventFormatter$LookupFieldPartFactory
index 3e1c180e971b45b5c371bcaf6635ec8e21cc47f9..851712b09aa3af8f8c3f2edb282f7de5d1695a90 100644 (file)
@@ -32,6 +32,8 @@ public class FOPException extends SAXException {
     private String systemId;
     private int line;
     private int column;
+    
+    private String localizedMessage;
 
     /**
      * Constructs a new FOP exception with the specified detail message.
@@ -210,5 +212,24 @@ public class FOPException extends SAXException {
             }
         }
     }
+    
+    /**
+     * Sets the localized message for this exception.
+     * @param msg the localized message
+     */
+    public void setLocalizedMessage(String msg) {
+        this.localizedMessage = msg;
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalizedMessage() {
+        if (this.localizedMessage != null) {
+            return this.localizedMessage;
+        } else {
+            return super.getLocalizedMessage();
+        }
+    }
 
+    
+    
 }
index a7405b466a7aaccef0e3a181d9f6fee7aaf24f8b..307087f7431f5a3962ebcd726d8377300908382a 100644 (file)
@@ -36,6 +36,12 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext;
 import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext;
 
 import org.apache.fop.Version;
+import org.apache.fop.events.DefaultEventBroadcaster;
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.FOPEventListenerProxy;
+import org.apache.fop.events.LoggingEventListener;
 import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.render.Renderer;
 import org.apache.fop.render.RendererFactory;
@@ -89,6 +95,7 @@ public class FOUserAgent {
     private Renderer rendererOverride = null;
     private FOEventHandler foEventHandlerOverride = null;
     private boolean locatorEnabled = true; // true by default (for error messages).
+    private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster();
     
     /** Producer:  Metadata element for the system/software that produces
      * the document. (Some renderers can store this in the document.)
@@ -530,5 +537,43 @@ public class FOUserAgent {
         return locatorEnabled;
     }
 
+    /**
+     * Returns the event broadcaster that control events sent inside a processing run. Clients
+     * can register event listeners with the event broadcaster to listen for events that occur
+     * while a document is being processed.
+     * @return the event broadcaster.
+     */
+    public EventBroadcaster getEventBroadcaster() {
+        return this.eventBroadcaster;
+    }
+
+    private class FOPEventBroadcaster extends DefaultEventBroadcaster {
+
+        private FOPEventListenerProxy rootListener;
+        
+        public FOPEventBroadcaster() {
+            this.rootListener = new FOPEventListenerProxy(
+                    this.listeners, FOUserAgent.this);
+        }
+        
+        /** {@inheritDoc} */
+        public void broadcastEvent(Event event) {
+            rootListener.processEvent(event);
+        }
+
+        /** {@inheritDoc} */
+        protected EventProducer createProxyFor(Class clazz) {
+            if (!this.listeners.hasEventListeners()) {
+                //Backwards-compatibility: Make sure at least the LoggingEventListener is plugged
+                //in so no events are just silently swallowed.
+                addEventListener(
+                        new LoggingEventListener(LogFactory.getLog(FOUserAgent.class)));
+                
+            }
+            return super.createProxyFor(clazz);
+        }
+        
+    }
+    
 }
 
diff --git a/src/java/org/apache/fop/area/AreaEventProducer.java b/src/java/org/apache/fop/area/AreaEventProducer.java
new file mode 100644 (file)
index 0000000..7747d2d
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.area;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+
+/**
+ * Event producer interface for events related to the area tree.
+ */
+public interface AreaEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static AreaEventProducer get(EventBroadcaster broadcaster) {
+            return (AreaEventProducer)broadcaster.getEventProducerFor(
+                    AreaEventProducer.class);
+        }
+    }
+
+    /**
+     * An unresolved ID reference was encountered.
+     * @param source the event source
+     * @param type the type of reference
+     * @param id the unresolved ID
+     * @event.severity WARN
+     */
+    void unresolvedIDReference(Object source, String type, String id);
+    
+    /**
+     * An unresolved ID reference was encountered on a page.
+     * @param source the event source
+     * @param page the page the ID reference was found on
+     * @param id the unresolved ID
+     * @event.severity WARN
+     */
+    void unresolvedIDReferenceOnPage(Object source, String page, String id);
+    
+    /**
+     * A page could not be loaded/deserialized from a file.
+     * @param source the event source
+     * @param page the page to be loaded
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void pageLoadError(Object source, String page, Exception e);
+    
+    /**
+     * A page could not be saved/serialized to a file.
+     * @param source the event source
+     * @param page the page to be serialized
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void pageSaveError(Object source, String page, Exception e);
+    
+    /**
+     * A page could not be rendered.
+     * @param source the event source
+     * @param page the page to be serialized
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void pageRenderingError(Object source, String page, Exception e);
+    
+}
index 7454f4667217696e01bf15db7c5b2e4b1eb1ea11..d3ea4155480403b00f340b0b99f42eee985412a2 100644 (file)
@@ -325,8 +325,9 @@ public class AreaTreeHandler extends FOEventHandler {
                 if (pageVPList != null) {
                     res.resolveIDRef(ids[count], pageVPList);
                 } else {
-                    log.warn(odi.getName() + ": Unresolved id reference \""
-                            + ids[count] + "\" found.");
+                    AreaEventProducer eventProducer = AreaEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                    eventProducer.unresolvedIDReference(this, odi.getName(), ids[count]);
                     idTracker.addUnresolvedIDRef(ids[count], res);
                 }
             }
index 94250debacf1e06d006fded2f35d88668ca9c5cf..1c2269a01a7d3cf3cc0da5ed135791de40788b1f 100644 (file)
@@ -23,7 +23,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
 
-import org.apache.fop.util.QName;
+import org.apache.xmlgraphics.util.QName;
 
 /**
  * Abstract base class for all area tree objects.
@@ -53,12 +53,12 @@ public abstract class AreaTreeObject {
         if (atts.size() == 0) {
             return;
         }
-        Iterator iter = atts.keySet().iterator();
+        Iterator iter = atts.entrySet().iterator();
         while (iter.hasNext()) {
-            QName qName = (QName)iter.next();
-            String value = (String)atts.get(qName);
+            Map.Entry entry = (Map.Entry)iter.next();
+            String value = (String)entry.getValue();
             //The casting is only to ensure type safety (too bad we can't use generics, yet) 
-            setForeignAttribute(qName, value);
+            setForeignAttribute((QName)entry.getKey(), value);
         }
     }
     
index fafb99ed6211d40c924e6d3e7b00f7eb0022b471..19edd3d5e177dfbafe2e3a69771b2bc025c76ba4 100644 (file)
@@ -50,6 +50,7 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.xmlgraphics.image.loader.ImageInfo;
 import org.apache.xmlgraphics.image.loader.ImageManager;
 import org.apache.xmlgraphics.image.loader.ImageSessionContext;
+import org.apache.xmlgraphics.util.QName;
 
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.area.Trait.Background;
@@ -78,7 +79,6 @@ import org.apache.fop.util.ColorUtil;
 import org.apache.fop.util.ContentHandlerFactory;
 import org.apache.fop.util.ContentHandlerFactoryRegistry;
 import org.apache.fop.util.DefaultErrorListener;
-import org.apache.fop.util.QName;
 
 /**
  * This is a parser for the area tree XML (intermediate format) which is used to reread an area
index 363fa02d107688b574704f4c1c8f060ccbd24540..b34a7e8d15dd499567bf93d6049f7c5bebce9cb2 100644 (file)
  
 package org.apache.fop.area;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.fop.apps.FOPException;
-import org.apache.fop.apps.FOUserAgent;
-import org.apache.fop.fonts.FontInfo;
-import org.xml.sax.SAXException;
-
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Iterator;
-
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.FileInputStream;
-import java.io.ObjectOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.OutputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedInputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.xml.sax.SAXException;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.events.ResourceEventProducer;
+import org.apache.fop.fonts.FontInfo;
 
 /**
  * A simple cached render pages model.
@@ -69,46 +72,39 @@ public class CachedRenderPagesModel extends RenderPagesModel {
      */
     protected boolean checkPreparedPages(PageViewport newpage, boolean renderUnresolved) {
         for (Iterator iter = prepared.iterator(); iter.hasNext();) {
-            PageViewport p = (PageViewport)iter.next();
-            if (p.isResolved() || renderUnresolved) {
-                if (p != newpage) {
+            PageViewport pageViewport = (PageViewport)iter.next();
+            if (pageViewport.isResolved() || renderUnresolved) {
+                if (pageViewport != newpage) {
                     try {
                         // load page from cache
-                        String name = (String)pageMap.get(p);
+                        String name = (String)pageMap.get(pageViewport);
                         File tempFile = new File(baseDir, name);
                         log.debug("Loading page from: " + tempFile);
                         ObjectInputStream in = new ObjectInputStream(
                                              new BufferedInputStream(
                                                new FileInputStream(tempFile)));
                         try {
-                            p.loadPage(in);
+                            pageViewport.loadPage(in);
                         } finally {
                             IOUtils.closeQuietly(in);
                         }
                         if (!tempFile.delete()) {
-                            log.warn("Temporary file could not be deleted: " + tempFile);
+                            ResourceEventProducer eventProducer
+                                = ResourceEventProducer.Provider.get(
+                                        renderer.getUserAgent().getEventBroadcaster());
+                            eventProducer.cannotDeleteTempFile(this, tempFile);
                         }
-                        pageMap.remove(p);
+                        pageMap.remove(pageViewport);
                     } catch (Exception e) {
-                        log.error(e);
+                        AreaEventProducer eventProducer
+                            = AreaEventProducer.Provider.get(
+                                renderer.getUserAgent().getEventBroadcaster());
+                        eventProducer.pageLoadError(this, pageViewport.getPageNumberString(), e);
                     }
                 }
 
-                try {
-                    renderer.renderPage(p);
-                    if (!p.isResolved()) {
-                        String[] idrefs = p.getIDRefs();
-                        for (int count = 0; count < idrefs.length; count++) {
-                            log.warn("Page " + p.getPageNumberString()
-                                + ": Unresolved id reference \"" + idrefs[count] 
-                                + "\" found.");
-                        }
-                    }
-                } catch (Exception e) {
-                    // use error handler to handle this FOP or IO Exception
-                    log.error(e);
-                }
-                p.clear();
+                renderPage(pageViewport);
+                pageViewport.clear();
                 iter.remove();
             } else {
                 if (!renderer.supportsOutOfOrder()) {
@@ -147,8 +143,11 @@ public class CachedRenderPagesModel extends RenderPagesModel {
             if (log.isDebugEnabled()) {
                 log.debug("Page saved to temporary file: " + tempFile);
             }
-        } catch (Exception e) {
-            log.error(e);
+        } catch (IOException ioe) {
+            AreaEventProducer eventProducer
+                = AreaEventProducer.Provider.get(
+                    renderer.getUserAgent().getEventBroadcaster());
+            eventProducer.pageSaveError(this, page.getPageNumberString(), ioe);
         }
     }
 
index af557ade9385725e7e2840e7c8eb778b907f3c72..da7ef1def81cbe399c0ac89f887854f717320241 100644 (file)
@@ -21,14 +21,15 @@ package org.apache.fop.area;
 
 import java.awt.Rectangle;
 import java.awt.geom.Rectangle2D;
-import java.io.ObjectOutputStream;
+import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
-import java.util.Map;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.logging.Log;
@@ -516,9 +517,9 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
      * The map of unresolved references are set on the page so that
      * the resolvers can be properly serialized and reloaded.
      * @param out the object output stream to write the contents
-     * @throws Exception if there is a problem saving the page
+     * @throws IOException in case of an I/O error while serializing the page
      */
-    public void savePage(ObjectOutputStream out) throws Exception {
+    public void savePage(ObjectOutputStream out) throws IOException {
         // set the unresolved references so they are serialized
         page.setUnresolvedReferences(unresolvedIDRefs);
         out.writeObject(page);
@@ -531,9 +532,10 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
      * if there are any unresolved references that were resolved
      * while saved they will be resolved on the page contents.
      * @param in the object input stream to read the page from
-     * @throws Exception if there is an error loading the page
+     * @throws ClassNotFoundException if a class was not found while loading the page
+     * @throws IOException if an I/O error occurred while loading the page
      */
-    public void loadPage(ObjectInputStream in) throws Exception {
+    public void loadPage(ObjectInputStream in) throws IOException, ClassNotFoundException {
         page = (Page) in.readObject();
         unresolvedIDRefs = page.getUnresolvedReferences();
         if (unresolvedIDRefs != null && pendingResolved != null) {
index e080e9cbe65a8b11b192d5e2ed29063df46b20ae..b215669022fd1f9fe6f0c49376b62f3a557eda9f 100644 (file)
@@ -156,23 +156,7 @@ public class RenderPagesModel extends AreaTreeModel {
                         && pageViewport.getPageSequence().isFirstPage(pageViewport)) {
                     renderer.startPageSequence(getCurrentPageSequence());
                 }
-                try {
-                    renderer.renderPage(pageViewport);
-                    if (!pageViewport.isResolved()) {
-                        String[] idrefs = pageViewport.getIDRefs();
-                        for (int count = 0; count < idrefs.length; count++) {
-                            log.warn("Page " + pageViewport.getPageNumberString()
-                                + ": Unresolved id reference \"" + idrefs[count] 
-                                + "\" found.");
-                        }
-                    }
-                } catch (Exception e) {
-                    // use error handler to handle this FOP or IO Exception
-                    log.error("Error while rendering page " + pageViewport.getPageIndex(), e);
-                    if (e instanceof RuntimeException) {
-                        throw (RuntimeException)e;
-                    }
-                }
+                renderPage(pageViewport);
                 pageViewport.clear();
                 iter.remove();
             } else {
@@ -185,6 +169,33 @@ public class RenderPagesModel extends AreaTreeModel {
         return renderer.supportsOutOfOrder() || prepared.isEmpty();
     }
 
+    /**
+     * Renders the given page and notified about unresolved IDs if any.
+     * @param pageViewport the page to be rendered.
+     */
+    protected void renderPage(PageViewport pageViewport) {
+        try {
+            renderer.renderPage(pageViewport);
+            if (!pageViewport.isResolved()) {
+                String[] idrefs = pageViewport.getIDRefs();
+                for (int count = 0; count < idrefs.length; count++) {
+                    AreaEventProducer eventProducer = AreaEventProducer.Provider.get(
+                            renderer.getUserAgent().getEventBroadcaster());
+                    eventProducer.unresolvedIDReferenceOnPage(this,
+                            pageViewport.getPageNumberString(), idrefs[count]);
+                }
+            }
+        } catch (Exception e) {
+            AreaEventProducer eventProducer = AreaEventProducer.Provider.get(
+                    renderer.getUserAgent().getEventBroadcaster());
+            eventProducer.pageRenderingError(this,
+                    pageViewport.getPageNumberString(), e);
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException)e;
+            }
+        }
+    }
+
     /**
      * Prepare a page.
      * An unresolved page can be prepared if the renderer supports
diff --git a/src/java/org/apache/fop/events/CompositeEventListener.java b/src/java/org/apache/fop/events/CompositeEventListener.java
new file mode 100644 (file)
index 0000000..a65728b
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.List;
+
+/**
+ * EventListener implementation forwards events to possibly multiple other EventListeners.
+ */
+public class CompositeEventListener implements EventListener {
+
+    private List listeners = new java.util.ArrayList();
+    
+    /**
+     * Adds an event listener to the broadcaster. It is appended to the list of previously
+     * registered listeners (the order of registration defines the calling order).
+     * @param listener the listener to be added
+     */
+    public synchronized void addEventListener(EventListener listener) {
+        this.listeners.add(listener);
+    }
+
+    /**
+     * Removes an event listener from the broadcaster. If the event listener is not registered,
+     * nothing happens.
+     * @param listener the listener to be removed
+     */
+    public synchronized void removeEventListener(EventListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    private synchronized int getListenerCount() {
+        return this.listeners.size();
+    }
+    
+    /**
+     * Indicates whether any listeners have been registered with the broadcaster.
+     * @return true if listeners are present, false otherwise
+     */
+    public boolean hasEventListeners() {
+        return (getListenerCount() > 0);
+    }
+    
+    /** {@inheritDoc} */
+    public synchronized void processEvent(Event event) {
+        for (int i = 0, c = getListenerCount(); i < c; i++) {
+            EventListener listener = (EventListener)this.listeners.get(i);
+            listener.processEvent(event);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/DefaultEventBroadcaster.java b/src/java/org/apache/fop/events/DefaultEventBroadcaster.java
new file mode 100644 (file)
index 0000000..bb1752a
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.xmlgraphics.util.Service;
+
+import org.apache.fop.events.model.EventMethodModel;
+import org.apache.fop.events.model.EventModel;
+import org.apache.fop.events.model.EventModelFactory;
+import org.apache.fop.events.model.EventProducerModel;
+import org.apache.fop.events.model.EventSeverity;
+
+/**
+ * Default implementation of the EventBroadcaster interface. It holds a list of event listeners
+ * and can provide {@link EventProducer} instances for type-safe event production.
+ */
+public class DefaultEventBroadcaster implements EventBroadcaster {
+
+    /** Holds all registered event listeners */
+    protected CompositeEventListener listeners = new CompositeEventListener();
+    
+    /** {@inheritDoc} */
+    public void addEventListener(EventListener listener) {
+        this.listeners.addEventListener(listener);
+    }
+
+    /** {@inheritDoc} */
+    public void removeEventListener(EventListener listener) {
+        this.listeners.removeEventListener(listener);
+    }
+
+    /** {@inheritDoc} */
+    public boolean hasEventListeners() {
+        return this.listeners.hasEventListeners();
+    }
+    
+    /** {@inheritDoc} */
+    public void broadcastEvent(Event event) {
+        this.listeners.processEvent(event);
+    }
+
+    private static List/*<EventModel>*/ eventModels = new java.util.ArrayList();
+    private Map proxies = new java.util.HashMap();
+    
+    static {
+        Iterator iter = Service.providers(EventModelFactory.class, true);
+        while (iter.hasNext()) {
+            EventModelFactory factory = (EventModelFactory)iter.next();
+            addEventModel(factory.createEventModel());
+        }
+    }
+
+    /**
+     * Adds a new {@link EventModel} to the list of registered event models.
+     * @param eventModel the event model instance
+     */
+    public static void addEventModel(EventModel eventModel) {
+        eventModels.add(eventModel);
+    }
+    
+    /** {@inheritDoc} */
+    public EventProducer getEventProducerFor(Class clazz) {
+        if (!EventProducer.class.isAssignableFrom(clazz)) {
+            throw new IllegalArgumentException(
+                    "Class must be an implementation of the EventProducer interface: "
+                    + clazz.getName());
+        }
+        EventProducer producer;
+        producer = (EventProducer)this.proxies.get(clazz);
+        if (producer == null) {
+            producer = createProxyFor(clazz);
+            this.proxies.put(clazz, producer);
+        }
+        return producer;
+    }
+    
+    private EventProducerModel getEventProducerModel(Class clazz) {
+        for (int i = 0, c = eventModels.size(); i < c; i++) {
+            EventModel eventModel = (EventModel)eventModels.get(i);
+            EventProducerModel producerModel = eventModel.getProducer(clazz);
+            if (producerModel != null) {
+                return producerModel;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Creates a dynamic proxy for the given EventProducer interface that will handle the
+     * conversion of the method call into the broadcasting of an event instance.
+     * @param clazz a descendant interface of EventProducer
+     * @return the EventProducer instance
+     */
+    protected EventProducer createProxyFor(Class clazz) {
+        final EventProducerModel producerModel = getEventProducerModel(clazz);
+        if (producerModel == null) {
+            throw new IllegalStateException("Event model doesn't contain the definition for "
+                    + clazz.getName());
+        }
+        return (EventProducer)Proxy.newProxyInstance(clazz.getClassLoader(),
+                new Class[] {clazz},
+                new InvocationHandler() {
+                    public Object invoke(Object proxy, Method method, Object[] args)
+                            throws Throwable {
+                        String methodName = method.getName();
+                        EventMethodModel methodModel = producerModel.getMethod(methodName);
+                        String eventID = producerModel.getInterfaceName() + "." + methodName;
+                        if (methodModel == null) {
+                            throw new IllegalStateException(
+                                    "Event model isn't consistent"
+                                    + " with the EventProducer interface. Please rebuild FOP!"
+                                    + " Affected method: "
+                                    + eventID);
+                        }
+                        Map params = new java.util.HashMap();
+                        int i = 1;
+                        Iterator iter = methodModel.getParameters().iterator();
+                        while (iter.hasNext()) {
+                            EventMethodModel.Parameter param
+                                = (EventMethodModel.Parameter)iter.next();
+                            params.put(param.getName(), args[i]);
+                            i++;
+                        }
+                        Event ev = new Event(args[0], eventID, methodModel.getSeverity(), params);
+                        broadcastEvent(ev);
+                        
+                        if (ev.getSeverity() == EventSeverity.FATAL) {
+                            EventExceptionManager.throwException(ev,
+                                    methodModel.getExceptionClass());
+                        }
+                        return null;
+                    }
+                });
+    }
+    
+}
diff --git a/src/java/org/apache/fop/events/Event.java b/src/java/org/apache/fop/events/Event.java
new file mode 100644 (file)
index 0000000..d3da180
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.Map;
+
+import org.apache.fop.events.model.EventSeverity;
+
+/**
+ * This is the default event class used by this package. Each event has a unique event identifier
+ * (a String), a severity indicator and a map of name/value pairs.
+ */
+public class Event extends EventObject {
+
+    private static final long serialVersionUID = -1310594422868258083L;
+    
+    private String eventID;
+    private EventSeverity severity;
+    private Map params;
+    
+    /**
+     * Creates a new Event.
+     * @param source the object that creates the event
+     * @param eventID the unique identifier of the event
+     * @param severity the severity level
+     * @param params the event parameters (a map of name/value pairs)
+     */
+    public Event(Object source, String eventID, EventSeverity severity, Map params) {
+        super(source);
+        this.eventID = eventID;
+        setSeverity(severity);
+        this.params = params;
+    }
+    
+    /**
+     * Returns the event identifier.
+     * @return the event identifier
+     */
+    public String getEventID() {
+        return this.eventID;
+    }
+    
+    /**
+     * Returns the event group identifier.
+     * @return the event group identifier (or null if there is no group identifier)
+     */
+    public String getEventGroupID() {
+        int pos = this.eventID.lastIndexOf('.');
+        if (pos > 0) {
+            return this.eventID.substring(0, pos);
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Returns the severity level.
+     * @return the severity level
+     */
+    public EventSeverity getSeverity() {
+        return this.severity;
+    }
+    
+    /**
+     * Sets the event's severity level. This method can be used to increase or decrease the
+     * severity level in a listener.
+     * @param severity the new event severity
+     */
+    public void setSeverity(EventSeverity severity) {
+        this.severity = severity;
+    }
+
+    /**
+     * Returns a parameter.
+     * @param key the key to the parameter
+     * @return the parameter value or null if no value with this key is found
+     */
+    public Object getParam(String key) {
+        if (this.params != null) {
+            return this.params.get(key);
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Returns an unmodifiable {@link java.util.Map} with all event parameters.
+     * @return the parameter map
+     */
+    public Map getParams() {
+        return Collections.unmodifiableMap(this.params);
+    }
+    
+    /**
+     * Creates and returns a fluent builder object for building up the parameter map.
+     * @return the parameter builder
+     */
+    public static ParamsBuilder paramsBuilder() {
+        return new ParamsBuilder();
+    }
+    
+    /**
+     * This class is a fluent builder class for building up the parameter map.
+     */
+    public static class ParamsBuilder {
+        private Map params;
+        
+        /**
+         * Adds a new parameter (a name/value pair).
+         * @param name the name of the parameter
+         * @param value the value of the parameter
+         * @return this instance
+         */
+        public ParamsBuilder param(String name, Object value) {
+            if (this.params == null) {
+                this.params = new java.util.HashMap();
+            }
+            this.params.put(name, value);
+            return this;
+        }
+        
+        /**
+         * Returns the accumulated parameter map.
+         * @return the accumulated parameter map
+         */
+        public Map build() {
+            return this.params;
+        }
+    }
+    
+}
diff --git a/src/java/org/apache/fop/events/EventBroadcaster.java b/src/java/org/apache/fop/events/EventBroadcaster.java
new file mode 100644 (file)
index 0000000..6c8df73
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+/**
+ * The EventBroadcaster is the central relay point for events. It receives events from various
+ * parts of the application and forwards them to any registered EventListener.
+ */
+public interface EventBroadcaster {
+
+    /**
+     * Adds an event listener to the broadcaster. It is appended to the list of previously
+     * registered listeners (the order of registration defines the calling order).
+     * @param listener the listener to be added
+     */
+    void addEventListener(EventListener listener);
+    
+    /**
+     * Removes an event listener from the broadcaster. If the event listener is not registered,
+     * nothing happens.
+     * @param listener the listener to be removed
+     */
+    void removeEventListener(EventListener listener);
+    /**
+     * Indicates whether any listeners have been registered with the broadcaster.
+     * @return true if listeners are present, false otherwise
+     */
+    boolean hasEventListeners();
+    
+    /**
+     * Broadcasts an event. This method is usually called from within the observed component.
+     * @param event the event to be broadcast
+     */
+    void broadcastEvent(Event event);
+    
+    /**
+     * Returns an event producer instance for the given interface class.
+     * @param clazz the Class object identifying an {@link EventProducer} interface
+     * @return the event producer instance
+     */
+    EventProducer getEventProducerFor(Class clazz);
+    
+}
diff --git a/src/java/org/apache/fop/events/EventExceptionManager.java b/src/java/org/apache/fop/events/EventExceptionManager.java
new file mode 100644 (file)
index 0000000..093ae70
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.xmlgraphics.util.Service;
+
+/**
+ * This class is reponsible for converting events into exceptions.
+ */
+public class EventExceptionManager {
+
+    private static final Map EXCEPTION_FACTORIES = new java.util.HashMap();
+    
+    static {
+        Iterator iter;
+        iter = Service.providers(ExceptionFactory.class, true);
+        while (iter.hasNext()) {
+            ExceptionFactory factory = (ExceptionFactory)iter.next();
+            EXCEPTION_FACTORIES.put(factory.getExceptionClass().getName(), factory);
+        }
+    }
+    
+    /**
+     * Converts an event into an exception and throws that. If the exception class is null,
+     * a {@link RuntimeException} will be thrown.
+     * @param event the event to be converted
+     * @param exceptionClass the exception class to be thrown
+     * @throws Throwable this happens always
+     */
+    public static void throwException(Event event, String exceptionClass) throws Throwable {
+        if (exceptionClass != null) {
+            ExceptionFactory factory = (ExceptionFactory)EXCEPTION_FACTORIES.get(exceptionClass);
+            if (factory != null) {
+                throw factory.createException(event);
+            } else {
+                throw new IllegalArgumentException(
+                        "No such ExceptionFactory available: " + exceptionClass);
+            }
+        } else {
+            String msg = EventFormatter.format(event);
+            throw new RuntimeException(msg);
+        }
+    }
+    
+    /**
+     * This interface is implementation by exception factories that can create exceptions from
+     * events.
+     */
+    public interface ExceptionFactory {
+        
+        /**
+         * Creates an exception from an event.
+         * @param event the event
+         * @return the newly created exception
+         */
+        Throwable createException(Event event);
+        
+        /**
+         * Returns the {@link Exception} class created by this factory.
+         * @return the exception class
+         */
+        Class getExceptionClass();
+    }
+}
diff --git a/src/java/org/apache/fop/events/EventFormatter.java b/src/java/org/apache/fop/events/EventFormatter.java
new file mode 100644 (file)
index 0000000..5696403
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.XMLResourceBundle;
+import org.apache.fop.util.text.AdvancedMessageFormat;
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Converts events into human-readable, localized messages.
+ */
+public final class EventFormatter {
+
+    private static final Pattern INCLUDES_PATTERN = Pattern.compile("\\{\\{.+\\}\\}");
+    
+    private static ResourceBundle defaultBundle = XMLResourceBundle.getXMLBundle(
+            EventFormatter.class.getName(), EventFormatter.class.getClassLoader());
+    
+    private static Log log = LogFactory.getLog(EventFormatter.class);
+    
+    private EventFormatter() {
+        //utility class
+    }
+    
+    /**
+     * Formats an event using the default locale.
+     * @param event the event
+     * @return the formatted message
+     */
+    public static String format(Event event) {
+        ResourceBundle bundle = null;
+        String groupID = event.getEventGroupID();
+        if (groupID != null) {
+            try {
+                 bundle = XMLResourceBundle.getXMLBundle(
+                        groupID,
+                        EventFormatter.class.getClassLoader());
+            } catch (MissingResourceException mre) {
+                if (log.isTraceEnabled()) {
+                    log.trace("No XMLResourceBundle for " + groupID + " available.");
+                }
+            }
+        }
+        if (bundle == null) {
+            bundle = defaultBundle;
+        }
+        return format(event, bundle);
+    }
+    
+    /**
+     * Formats an event using a given locale.
+     * @param event the event
+     * @param locale the locale
+     * @return the formatted message
+     */
+    public static String format(Event event, Locale locale) {
+        ResourceBundle bundle = null;
+        String groupID = event.getEventGroupID();
+        if (groupID != null) {
+            try {
+                 bundle = XMLResourceBundle.getXMLBundle(
+                        groupID, locale,
+                        EventFormatter.class.getClassLoader());
+            } catch (MissingResourceException mre) {
+                if (log.isTraceEnabled()) {
+                    log.trace("No XMLResourceBundle for " + groupID + " available.");
+                }
+            }
+        }
+        if (bundle == null) {
+            bundle = XMLResourceBundle.getXMLBundle(
+                    EventFormatter.class.getName(),
+                    locale,
+                    EventFormatter.class.getClassLoader());
+        }
+        return format(event, bundle);
+    }
+
+    private static String format(Event event, ResourceBundle bundle) {
+        String template = bundle.getString(event.getEventID());
+        return format(event, processIncludes(template, bundle));
+    }
+
+    private static String processIncludes(String template, ResourceBundle bundle) {
+        CharSequence input = template;
+        int replacements;
+        StringBuffer sb;
+        do {
+            sb = new StringBuffer(Math.max(16, input.length()));
+            replacements = processIncludesInner(input, sb, bundle);
+            input = sb;
+        } while (replacements > 0);
+        String s = sb.toString();
+        return s;
+    }
+
+    private static int processIncludesInner(CharSequence template, StringBuffer sb,
+            ResourceBundle bundle) {
+        int replacements = 0;
+        Matcher m = INCLUDES_PATTERN.matcher(template);
+        while (m.find()) {
+            String include = m.group();
+            include = include.substring(2, include.length() - 2);
+            m.appendReplacement(sb, bundle.getString(include));
+            replacements++;
+        }
+        m.appendTail(sb);
+        return replacements;
+    }
+
+    /**
+     * Formats the event using a given pattern. The pattern needs to be compatible with
+     * {@link AdvancedMessageFormat}.
+     * @param event the event
+     * @param pattern the pattern (compatible with {@link AdvancedMessageFormat})
+     * @return the formatted message
+     */
+    public static String format(Event event, String pattern) {
+        AdvancedMessageFormat format = new AdvancedMessageFormat(pattern);
+        Map params = new java.util.HashMap(event.getParams());
+        params.put("source", event.getSource());
+        params.put("severity", event.getSeverity());
+        return format.format(params);
+    }
+    
+    private static class LookupFieldPart implements Part {
+        
+        private String fieldName;
+        
+        public LookupFieldPart(String fieldName) {
+            this.fieldName = fieldName;
+        }
+
+        public boolean isGenerated(Map params) {
+            return getKey(params) != null;
+        }
+
+        public void write(StringBuffer sb, Map params) {
+            sb.append(defaultBundle.getString(getKey(params)));
+        }
+
+        private String getKey(Map params) {
+            return (String)params.get(fieldName);
+        }
+        
+        /** {@inheritDoc} */
+        public String toString() {
+            return "{" + this.fieldName + ", lookup}";
+        }
+        
+    }
+    
+    /** PartFactory for lookups. */
+    public static class LookupFieldPartFactory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new LookupFieldPart(fieldName);
+        }
+
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "lookup";
+        }
+        
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/EventFormatter.xml b/src/java/org/apache/fop/events/EventFormatter.xml
new file mode 100644 (file)
index 0000000..f17da11
--- /dev/null
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<!-- $Id$ -->
+<catalogue xml:lang="en">
+  <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message>
+  <message key="rule.markerDescendantOfFlow">An fo:marker is permitted only as the descendant of an fo:flow.</message>
+  <message key="rule.retrieveMarkerDescendatOfStaticContent">An fo:retrieve-marker is permitted only as the descendant of an fo:static-content.</message>
+  <message key="rule.bidiOverrideContent">An fo:bidi-override that is a descendant of an fo:leader or of the fo:inline child of an fo:footnote may not have block-level children, unless it has a nearer ancestor that is an fo:inline-container.</message>
+  <message key="rule.inlineContent">An fo:inline that is a descendant of an fo:leader or fo:footnote may not have block-level children, unless it has a nearer ancestor that is an fo:inline-container.</message>
+  <message key="rule.childOfSPM">The element must be a child of fo:simple-page-master.</message>
+  <message key="rule.childOfDeclarations">The element must be a child of fo:declarations.</message>
+  <message key="rule.childOfSPMorDeclarations">The element must be a child of fo:declarations or fo:simple-page-master.</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">For "{elementName}", only one "{offendingNode}" may be declared.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.nodeOutOfOrder">For "{elementName}", "{tooLateNode}" must be declared before "{tooEarlyNode}"!{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.invalidChild">"{offendingNode}" is not a valid child element of "{elementName}"![ {ruleViolated,lookup}]{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.missingChildElement">"{elementName}" is missing child elements.[
+Required content model: {contentModel}]{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Element "{elementName}" is missing required property "{propertyName}"!{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.idNotUnique">Property ID "{id}" (found on "{elementName}") previously used; ID values must be unique within a document!{severity,equals,EventSeverity:FATAL,,
+Any reference to it will be considered a reference to the first occurrence in the document.}{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.markerNotInitialChild">fo:marker must be an initial child: {mcname}{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.markerNotUniqueForSameParent">fo:marker "marker-class-name" must be unique for same parent: {mcname}{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.invalidProperty">Invalid property encountered on "{elementName}": {attr}{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.invalidPropertyValue">Invalid property value encountered in {propName}="{propValue}"[: {e}]{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.unimplementedFeature">The following feature isn't implemented by Apache FOP, yet: {feature} (on {elementName}){{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.missingLinkDestination">Missing attribute on {elementName}: Either external-destination or internal-destination must be specified.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.markerCloningFailed">Unable to clone subtree of fo:marker (marker-class-name="{markerClassName}") for fo:retrieve-marker.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.colorProfileNameNotUnique">Duplicate color profile profile name: {name}{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.regionNameMappedToMultipleRegionClasses">Region-name ("{regionName}") is being mapped to multiple region-classes ({defaultRegionClass1} and {defaultRegionClass2}).{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.masterNameNotUnique">The page master name ("{name}") must be unique across page-masters and page-sequence-masters.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.duplicateFlowNameInPageSequence">Duplicate flow-name "{flowName}" found within {elementName}.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.flowNameNotMapped">The flow-name "{flowName}" on {elementName} could not be mapped to a region-name in the layout-master-set.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.masterNotFound">The master-reference "{masterReference}" on {elementName} matches no simple-page-master or page-sequence-master.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.illegalRegionName">The region-name "{regionName}" for {elementName} is not permitted.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.nonZeroBorderPaddingOnRegion">Border and padding for {elementName} "{regionName}" must be '0' (See 6.4.13 in XSL 1.0).{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.columnCountErrorOnRegionBodyOverflowScroll">If overflow property is set to "scroll" on {elementName}, a column-count other than "1" may not be specified.{{locator}}</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.invalidFORoot">First element must be the fo:root formatting object. Found {elementName} instead. Please make sure you're producing a valid XSL-FO document.</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.emptyDocument">Document is empty (something might be wrong with your XSLT stylesheet).</message>
+  <message key="org.apache.fop.fo.FOValidationEventProducer.unknownFormattingObject">Unknown formatting object "{offendingNode}" encountered (a child of {elementName}}.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.nonAutoBPDOnTable">Only a value of "auto" for block-progression-dimension has a well-specified behavior on fo:table. Falling back to "auto".{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.noTablePaddingWithCollapsingBorderModel">In collapsing border model a table does not have padding (see http://www.w3.org/TR/REC-CSS2/tables.html#collapsing-borders), but a non-zero value for padding was found. The padding will be ignored.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.noMixRowsAndCells">Either fo:table-rows or fo:table-cells may be children of an {elementName} but not both.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.footerOrderCannotRecover">This table uses the collapsing border model. In order to resolve borders in an efficient way the table-footer must be known before any table-body is parsed. Either put the footer at the correct place or switch to the separate border model.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.startEndRowUnderTableRowWarning">starts-row/ends-row for fo:table-cells non-applicable for children of an fo:table-row.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.tooManyCells">The column-number or number of cells in the row overflows the number of fo:table-columns specified for the table.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.valueMustBeBiggerGtEqOne">{propName} must be 1 or bigger, but got {actualValue}{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.warnImplicitColumns">table-layout=\"fixed\" and column-width unspecified =&gt; falling back to proportional-column-width(1){{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.paddingNotApplicable">padding-* properties are not applicable to {elementName}, but a non-zero value for padding was found.{{locator}}</message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.cellOverlap">{elementName} overlaps in column {column}.<!-- no locator here, exception will be wrapped --></message>
+  <message key="org.apache.fop.fo.flow.table.TableEventProducer.breakIgnoredDueToRowSpanning">{breakBefore,if,break-before,break-after} ignored on {elementName} because of row spanning in progress (See XSL 1.1, {breakBefore,if,7.20.2,7.20.1}){{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.imageNotFound">Image not found.[ URI: {uri}.]{{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.imageError">Image not available.[ URI: {uri}.] Reason:[ {reason}][ {e}]{{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.imageIOError">I/O error while loading image.[ URI: {uri}.][ Reason: {ioe}]{{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.ifoNoIntrinsicSize">The intrinsic dimensions of an instream-foreign-object could not be determined.{{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.uriError">Error while handling URI: {uri}. Reason: {e}{{locator}}</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.foreignXMLProcessingError">Some XML content will be ignored. Could not render XML in namespace "{namespaceURI}".[ Reason: {e}]</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.foreignXMLNoHandler">Some XML content will be ignored. No handler defined for XML with namespace "{namespaceURI}".</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.imageWritingError">Error while writing an image to the target file.[ Reason: {e}]</message>
+  <message key="org.apache.fop.events.ResourceEventProducer.cannotDeleteTempFile">Temporary file could not be deleted: {tempFile}</message>
+  <message key="org.apache.fop.layoutmgr.inline.InlineLevelEventProducer.leaderWithoutContent">fo:leader is set to "use-content" but has no content.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.inline.InlineLevelEventProducer.lineOverflows">Line {line} of a paragraph overflows the available area by {overflowLength,choice,50000#{overflowLength} millipoints|50000&lt;more than 50 points}.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.rowTooTall">The contents of table-row {row} are taller than they should be (there is a block-progression-dimension or height constraint on the indicated row). Due to its contents the row grows to {effCellBPD} millipoints, but the row shouldn't get any taller than {maxCellBPD} millipoints.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.tableFixedAutoWidthNotSupported">table-layout="fixed" and width="auto", but auto-layout not supported =&gt; assuming width="100%".{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.objectTooWide">The extent in inline-progression-direction (width) of a {elementName} is bigger than the available space ({effIPD}mpt &gt; {maxIPD}mpt).{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.overconstrainedAdjustEndIndent">Adjusting end-indent based on overconstrained geometry rules for {elementName}.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.viewportOverflow">Content overflows the viewport of an {elementName} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.regionOverflow">Content overflows the viewport of the {elementName} on page {page} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.flowNotMappingToRegionBody">Flow "{flowName}" does not map to the region-body in page-master "{masterName}". FOP presently does not support this.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.pageSequenceMasterExhausted">Subsequences exhausted in page-sequence-master "{pageSequenceMasterName}", {canRecover,if,using previous subsequence,cannot recover}.{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.missingSubsequencesInPageSequenceMaster">No subsequences in page-sequence-master "{pageSequenceMasterName}".{{locator}}</message>
+  <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.noMatchingPageMaster">No simple-page-master matching "{pageMasterName}" in page-sequence-master "{pageSequenceMasterName}".{{locator}}</message>
+  <message key="org.apache.fop.svg.SVGEventProducer.error">SVG error: {message}</message>
+  <message key="org.apache.fop.svg.SVGEventProducer.alert">SVG alert: {message}</message>
+  <message key="org.apache.fop.svg.SVGEventProducer.info">SVG info: {message}</message>
+  <message key="org.apache.fop.svg.SVGEventProducer.svgNotBuilt">SVG graphic could not be built. Reason: {e}</message>
+  <message key="org.apache.fop.svg.SVGEventProducer.svgRenderingError">SVG graphic could not be rendered. Reason: {e}</message>
+  <message key="org.apache.fop.render.RendererEventProducer.ioError">I/O error while writing to target file.[ Reason: {ioe}]</message>
+  <message key="org.apache.fop.area.AreaEventProducer.unresolvedIDReference">{type}: Unresolved ID reference "{id}" found.</message>
+  <message key="org.apache.fop.area.AreaEventProducer.unresolvedIDReferenceOnPage">Page {page}: Unresolved ID reference "{id}" found.</message>
+  <message key="org.apache.fop.area.AreaEventProducer.pageLoadError">Error while deserializing page {page}.[ Reason: {e}]</message>
+  <message key="org.apache.fop.area.AreaEventProducer.pageSaveError">Error while serializing page {page}.[ Reason: {e}]</message>
+  <message key="org.apache.fop.area.AreaEventProducer.pageRenderingError">Error while rendering page {page}.[ Reason: {e}]</message>
+  <message key="org.apache.fop.fonts.FontEventAdapter.fontSubstituted">Font "{requested}" not found. Substituting with "{effective}".</message>
+  <message key="org.apache.fop.fonts.FontEventAdapter.fontLoadingErrorAtAutoDetection">Unable to load font file: {fontURL}.[ Reason: {e}]</message>
+  <message key="org.apache.fop.fonts.FontEventAdapter.glyphNotAvailable">Glyph "{ch}" (0x{ch,hex}[, {ch,glyph-name}]) not available in font "{fontName}".</message>
+</catalogue>
diff --git a/src/java/org/apache/fop/events/EventFormatter_de.xml b/src/java/org/apache/fop/events/EventFormatter_de.xml
new file mode 100644 (file)
index 0000000..c65d24f
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+    Licensed to the Apache Software Foundation (ASF) under one or more\r
+    contributor license agreements.  See the NOTICE file distributed with\r
+    this work for additional information regarding copyright ownership.\r
+    The ASF licenses this file to You under the Apache License, Version 2.0\r
+    (the "License"); you may not use this file except in compliance with\r
+    the License.  You may obtain a copy of the License at\r
+    \r
+    http://www.apache.org/licenses/LICENSE-2.0\r
+    \r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+-->\r
+<!-- $Id$ -->\r
+<catalogue xml:lang="de">\r
+  <message key="locator">[ (Siehe Position {loc})| (Siehe {#gatherContextInfo})| (Keine Kontextinformationen verfügbar)]</message>\r
+  <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">In "{elementName}" darf nur ein einziges "{offendingNode}" vorkommen!{{locator}}</message>\r
+  <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Dem Element "{elementName}" fehlt ein verlangtes Property "{propertyName}"!{{locator}}</message>\r
+</catalogue>\r
diff --git a/src/java/org/apache/fop/events/EventListener.java b/src/java/org/apache/fop/events/EventListener.java
new file mode 100644 (file)
index 0000000..f8293ae
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+/**
+ * This interface is implemented by clients who want to listen for events.
+ */
+public interface EventListener extends java.util.EventListener {
+
+    /**
+     * This method is called for each event that is generated. With the event's ID it is possible
+     * to react to certain events. Events can also simply be recorded and presented to a user.
+     * It is possible to throw an (unchecked) exception if the processing needs to be aborted
+     * because some special event occured. This way the client can configure the behaviour of
+     * the observed application.
+     * @param event the event
+     */
+    void processEvent(Event event);
+    
+}
diff --git a/src/java/org/apache/fop/events/EventProducer.java b/src/java/org/apache/fop/events/EventProducer.java
new file mode 100644 (file)
index 0000000..88da771
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+/**
+ * This is a marker interface which all event producer interfaces need to extend. These interfaces
+ * must agree to the following convention:
+ * <ul>
+ *   <li>The first parameter of each method must be: <code>Object source</code>
+ * </ul>
+ */
+public interface EventProducer {
+
+}
diff --git a/src/java/org/apache/fop/events/FOPEventListenerProxy.java b/src/java/org/apache/fop/events/FOPEventListenerProxy.java
new file mode 100644 (file)
index 0000000..d4c2378
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.events.model.EventSeverity;
+import org.apache.fop.fo.FOValidationEventProducer;
+import org.apache.fop.layoutmgr.BlockLevelEventProducer;
+
+/**
+ * EventListener proxy that inspects all events and adjusts severity levels where necessary.
+ * For validation events, it reacts on each event based on the strict validation setting in
+ * the user agent.
+ * For layout events, it reduces the default severity level if FOP signals that it can recover
+ * from the event. 
+ */
+public class FOPEventListenerProxy implements EventListener {
+
+    private static final String FOVALIDATION_EVENT_ID_PREFIX
+                = FOValidationEventProducer.class.getName();
+    
+    private static final String BLOCK_LEVEL_EVENT_ID_PREFIX
+                = BlockLevelEventProducer.class.getName();
+
+    private EventListener delegate;
+    private FOUserAgent userAgent;
+    
+    /**
+     * Main constructor.
+     * @param delegate the event listener to delegate events to 
+     * @param userAgent the FO user agent
+     */
+    public FOPEventListenerProxy(EventListener delegate, FOUserAgent userAgent) {
+        this.delegate = delegate;
+        this.userAgent = userAgent;
+    }
+    
+    /** {@inheritDoc} */
+    public synchronized void processEvent(Event event) {
+        if (event.getEventID().startsWith(FOVALIDATION_EVENT_ID_PREFIX)) {
+            Boolean canRecover = (Boolean)event.getParam("canRecover");
+            if (Boolean.TRUE.equals(canRecover) && !userAgent.validateStrictly()) {
+                //Reduce severity if FOP can recover
+                event.setSeverity(EventSeverity.WARN);
+            }
+        } else if (event.getEventID().startsWith(BLOCK_LEVEL_EVENT_ID_PREFIX)) {
+            Boolean canRecover = (Boolean)event.getParam("canRecover");
+            if (Boolean.TRUE.equals(canRecover)) {
+                //Reduce severity if FOP can recover
+                event.setSeverity(EventSeverity.WARN);
+            }
+        }
+        this.delegate.processEvent(event);
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/FOPEventModelFactory.java b/src/java/org/apache/fop/events/FOPEventModelFactory.java
new file mode 100644 (file)
index 0000000..5a75042
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Factory for FOP's main event model.
+ */
+public class FOPEventModelFactory extends AbstractEventModelFactory {
+
+    private static final String EVENT_MODEL_FILENAME = "event-model.xml";
+
+    /** {@inheritDoc} */
+    public EventModel createEventModel() {
+        return loadModel(getClass(), EVENT_MODEL_FILENAME);
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/LoggingEventListener.java b/src/java/org/apache/fop/events/LoggingEventListener.java
new file mode 100644 (file)
index 0000000..9ba8ed2
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.events.model.EventSeverity;
+
+/**
+ * EventListener implementation that redirects events to Commons Logging. The events are
+ * converted to localized messages.
+ */
+public class LoggingEventListener implements EventListener {
+
+    /** Default logger instance */
+    private static Log defaultLog = LogFactory.getLog(LoggingEventListener.class);
+    
+    private Log log;
+    private boolean skipFatal;
+    
+    /**
+     * Creates an instance logging to the default log category of this class.
+     */
+    public LoggingEventListener() {
+        this(defaultLog);
+    }
+    
+    /**
+     * Creates an instance logging to a given logger. Events with fatal severity level will be
+     * skipped.
+     * @param log the target logger
+     */
+    public LoggingEventListener(Log log) {
+        this(log, true);
+    }
+    
+    /**
+     * Creates an instance logging to a given logger.
+     * @param log the target logger
+     * @param skipFatal true if events with fatal severity level should be skipped (i.e. not logged)
+     */
+    public LoggingEventListener(Log log, boolean skipFatal) {
+        this.log = log;
+        this.skipFatal = skipFatal;
+    }
+    
+    /**
+     * Returns the target logger for this instance.
+     * @return the target logger
+     */
+    public Log getLog() {
+        return this.log;
+    }
+    
+    /** {@inheritDoc} */
+    public void processEvent(Event event) {
+        String msg = EventFormatter.format(event);
+        EventSeverity severity = event.getSeverity();
+        if (severity == EventSeverity.INFO) {
+            log.info(msg);
+        } else if (severity == EventSeverity.WARN) {
+            log.warn(msg);
+        } else if (severity == EventSeverity.ERROR) {
+            log.error(msg);
+        } else if (severity == EventSeverity.FATAL) {
+            if (!skipFatal) {
+                log.fatal(msg);
+            }
+        } else {
+            assert false;
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/PropertyExceptionFactory.java b/src/java/org/apache/fop/events/PropertyExceptionFactory.java
new file mode 100644 (file)
index 0000000..667c4a1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Locale;
+
+import org.apache.fop.events.EventExceptionManager.ExceptionFactory;
+import org.apache.fop.fo.expr.PropertyException;
+
+/**
+ * Exception factory for {@link PropertyException}.
+ */
+public class PropertyExceptionFactory implements ExceptionFactory {
+
+    /** {@inheritDoc} */
+    public Throwable createException(Event event) {
+        String msg = EventFormatter.format(event, Locale.ENGLISH);
+        PropertyException ex = new PropertyException(msg);
+        if (!Locale.ENGLISH.equals(Locale.getDefault())) {
+            ex.setLocalizedMessage(EventFormatter.format(event));
+        }
+        return ex;
+    }
+    
+    /** {@inheritDoc} */
+    public Class getExceptionClass() {
+        return PropertyException.class;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/events/ResourceEventProducer.java b/src/java/org/apache/fop/events/ResourceEventProducer.java
new file mode 100644 (file)
index 0000000..21da4f1
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.Locator;
+
+import org.apache.xmlgraphics.image.loader.ImageException;
+
+/**
+ * Event producer interface for resource events (missing images, fonts etc.).
+ */
+public interface ResourceEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the requested event producer
+         */
+        public static ResourceEventProducer get(EventBroadcaster broadcaster) {
+            return (ResourceEventProducer)broadcaster.getEventProducerFor(
+                    ResourceEventProducer.class);
+        }
+    }
+
+    /**
+     * Image not found.
+     * @param source the event source
+     * @param uri the original URI of the image
+     * @param fnfe the "file not found" exception
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void imageNotFound(Object source, String uri, FileNotFoundException fnfe, Locator loc);
+    
+    /**
+     * Error while processing image.
+     * @param source the event source
+     * @param uri the original URI of the image
+     * @param e the image exception
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void imageError(Object source, String uri, ImageException e, Locator loc);
+    
+    /**
+     * I/O error while loading an image.
+     * @param source the event source
+     * @param uri the original URI of the image
+     * @param ioe the I/O exception
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void imageIOError(Object source, String uri, IOException ioe, Locator loc);
+
+    /**
+     * Error while writing/serializing an image to an output format.
+     * @param source the event source
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void imageWritingError(Object source, Exception e);
+
+    /**
+     * Error while handling a URI.
+     * @param source the event source
+     * @param uri the original URI of the image
+     * @param e the original exception
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void uriError(Object source, String uri, Exception e, Locator loc);
+
+    /**
+     * Intrinsic size of fo:instream-foreign-object could not be determined.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void ifoNoIntrinsicSize(Object source, Locator loc);
+    
+    /**
+     * Error processing foreign XML content.
+     * @param source the event source
+     * @param doc the foreign XML
+     * @param namespaceURI the namespace URI of the foreign XML
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void foreignXMLProcessingError(Object source, Document doc, String namespaceURI, Exception e);
+
+    /**
+     * No handler for foreign XML content.
+     * @param source the event source
+     * @param doc the foreign XML
+     * @param namespaceURI the namespace URI of the foreign XML
+     * @event.severity ERROR
+     */
+    void foreignXMLNoHandler(Object source, Document doc, String namespaceURI);
+    
+    /**
+     * Cannot delete a temporary file.
+     * @param source the event source
+     * @param tempFile the temporary file
+     * @event.severity ERROR
+     */
+    void cannotDeleteTempFile(Object source, File tempFile);
+    
+}
diff --git a/src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java b/src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java
new file mode 100644 (file)
index 0000000..06ce8dd
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Locale;
+
+import org.apache.fop.events.EventExceptionManager.ExceptionFactory;
+
+/**
+ * Exception factory for {@link UnsupportedOperationException}.
+ */
+public class UnsupportedOperationExceptionFactory implements ExceptionFactory {
+
+    /** {@inheritDoc} */
+    public Throwable createException(Event event) {
+        String msg = EventFormatter.format(event, Locale.ENGLISH);
+        UnsupportedOperationException ex = new UnsupportedOperationException(msg);
+        return ex;
+    }
+    
+    /** {@inheritDoc} */
+    public Class getExceptionClass() {
+        return UnsupportedOperationException.class;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/events/ValidationExceptionFactory.java b/src/java/org/apache/fop/events/ValidationExceptionFactory.java
new file mode 100644 (file)
index 0000000..9dba840
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import java.util.Locale;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.events.EventExceptionManager.ExceptionFactory;
+import org.apache.fop.fo.ValidationException;
+
+/**
+ * Exception factory for {@link ValidationException}.
+ */
+public class ValidationExceptionFactory implements ExceptionFactory {
+
+    /** {@inheritDoc} */
+    public Throwable createException(Event event) {
+        Locator loc = (Locator)event.getParam("loc");
+        String msg = EventFormatter.format(event, Locale.ENGLISH);
+        ValidationException ex = new ValidationException(msg, loc);
+        if (!Locale.ENGLISH.equals(Locale.getDefault())) {
+            ex.setLocalizedMessage(EventFormatter.format(event));
+        }
+        return ex;
+    }
+    
+    /** {@inheritDoc} */
+    public Class getExceptionClass() {
+        return ValidationException.class;
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/events/model/AbstractEventModelFactory.java b/src/java/org/apache/fop/events/model/AbstractEventModelFactory.java
new file mode 100644 (file)
index 0000000..ee980f3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.io.InputStream;
+import java.util.MissingResourceException;
+
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.events.DefaultEventBroadcaster;
+
+/**
+ * This interface is used to instantiate (load, parse) event models.
+ */
+public abstract class AbstractEventModelFactory implements EventModelFactory {
+
+    /**
+     * Loads an event model and returns its instance.
+     * @param resourceBaseClass base class to use for loading resources
+     * @param resourceName the resource name pointing to the event model to be loaded
+     * @return the newly loaded event model.
+     */
+    public EventModel loadModel(Class resourceBaseClass, String resourceName) {
+        InputStream in = resourceBaseClass.getResourceAsStream(resourceName);
+        if (in == null) {
+            throw new MissingResourceException(
+                    "File " + resourceName + " not found",
+                    DefaultEventBroadcaster.class.getName(), ""); 
+        }
+        try {
+            return EventModelParser.parse(new StreamSource(in));
+        } catch (TransformerException e) {
+            throw new MissingResourceException(
+                    "Error reading " + resourceName + ": " + e.getMessage(),
+                    DefaultEventBroadcaster.class.getName(), ""); 
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+    
+}
diff --git a/src/java/org/apache/fop/events/model/EventMethodModel.java b/src/java/org/apache/fop/events/model/EventMethodModel.java
new file mode 100644 (file)
index 0000000..930cda5
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * Represents an event method. Each method in an event producer interface will result in one
+ * instance of <code>EventMethodModel</code>.
+ */
+public class EventMethodModel implements Serializable, XMLizable {
+
+    private static final long serialVersionUID = -7548882973341444354L;
+    
+    private String methodName;
+    private EventSeverity severity;
+    private List params = new java.util.ArrayList();
+    private String exceptionClass;
+    
+    /**
+     * Creates an new instance.
+     * @param methodName the event method's name
+     * @param severity the event severity
+     */
+    public EventMethodModel(String methodName, EventSeverity severity) {
+        this.methodName = methodName;
+        this.severity = severity;
+    }
+    
+    /**
+     * Adds a method parameter.
+     * @param param the method parameter
+     */
+    public void addParameter(Parameter param) {
+        this.params.add(param);
+    }
+    
+    /**
+     * Adds a method parameter.
+     * @param type the type of the parameter
+     * @param name the name of the parameter
+     * @return the resulting Parameter instance
+     */
+    public Parameter addParameter(Class type, String name) {
+        Parameter param = new Parameter(type, name); 
+        addParameter(param);
+        return param;
+    }
+    
+    /**
+     * Sets the event method name.
+     * @param name the event name
+     */
+    public void setMethodName(String name) {
+        this.methodName = name;
+    }
+    
+    /**
+     * Returns the event method name
+     * @return the event name
+     */
+    public String getMethodName() {
+        return this.methodName;
+    }
+    
+    /**
+     * Sets the event's severity level.
+     * @param severity the severity
+     */
+    public void setSeverity(EventSeverity severity) {
+        this.severity = severity;
+    }
+    
+    /**
+     * Returns the event's severity level.
+     * @return the severity
+     */
+    public EventSeverity getSeverity() {
+        return this.severity;
+    }
+    
+    /**
+     * Returns an unmodifiable list of parameters for this event method.
+     * @return the list of parameters
+     */
+    public List getParameters() {
+        return Collections.unmodifiableList(this.params);
+    }
+    
+    /**
+     * Sets the primary exception class for this event method. Note: Not all event methods throw
+     * exceptions!
+     * @param exceptionClass the exception class
+     */
+    public void setExceptionClass(String exceptionClass) {
+        this.exceptionClass = exceptionClass;
+    }
+    
+    /**
+     * Returns the primary exception class for this event method. This method returns null if
+     * the event is only informational or just a warning.
+     * @return the primary exception class or null
+     */
+    public String getExceptionClass() {
+        return this.exceptionClass;
+    }
+    
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(null, "name", "name", "CDATA", getMethodName());
+        atts.addAttribute(null, "severity", "severity", "CDATA", getSeverity().getName());
+        if (getExceptionClass() != null) {
+            atts.addAttribute(null, "exception", "exception", "CDATA", getExceptionClass());
+        }
+        String elName = "method";
+        handler.startElement(null, elName, elName, atts);
+        Iterator iter = this.params.iterator();
+        while (iter.hasNext()) {
+            ((XMLizable)iter.next()).toSAX(handler);
+        }
+        handler.endElement(null, elName, elName);
+    }
+    
+    /**
+     * Represents an event parameter.
+     */
+    public static class Parameter implements Serializable, XMLizable {
+        
+        private static final long serialVersionUID = 6062500277953887099L;
+        
+        private Class type;
+        private String name;
+        
+        /**
+         * Creates a new event parameter.
+         * @param type the parameter type
+         * @param name the parameter name
+         */
+        public Parameter(Class type, String name) {
+            this.type = type;
+            this.name = name;
+        }
+        
+        /**
+         * Returns the parameter type.
+         * @return the parameter type
+         */
+        public Class getType() {
+            return this.type;
+        }
+        
+        /**
+         * Returns the parameter name.
+         * @return the parameter name
+         */
+        public String getName() {
+            return this.name;
+        }
+
+        /** {@inheritDoc} */
+        public void toSAX(ContentHandler handler) throws SAXException {
+            AttributesImpl atts = new AttributesImpl();
+            atts.addAttribute(null, "type", "type", "CDATA", getType().getName());
+            atts.addAttribute(null, "name", "name", "CDATA", getName());
+            String elName = "parameter";
+            handler.startElement(null, elName, elName, atts);
+            handler.endElement(null, elName, elName);
+        }
+        
+    }
+}
diff --git a/src/java/org/apache/fop/events/model/EventModel.java b/src/java/org/apache/fop/events/model/EventModel.java
new file mode 100644 (file)
index 0000000..61e221b
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * Represents a whole event model that supports multiple event producers.
+ */
+public class EventModel implements Serializable, XMLizable {
+
+    private static final long serialVersionUID = 7468592614934605082L;
+    
+    private Map producers = new java.util.LinkedHashMap();
+    
+    /**
+     * Creates a new, empty event model
+     */
+    public EventModel() {
+    }
+    
+    /**
+     * Adds the model of an event producer to the event model.
+     * @param producer the event producer model
+     */
+    public void addProducer(EventProducerModel producer) {
+        this.producers.put(producer.getInterfaceName(), producer);
+    }
+    
+    /**
+     * Returns an iterator over the contained event producer models.
+     * @return an iterator (Iterator&lt;EventProducerModel&gt;)
+     */
+    public Iterator getProducers() {
+        return this.producers.values().iterator();
+    }
+
+    /**
+     * Returns the model of an event producer with the given interface name.
+     * @param interfaceName the fully qualified name of the event producer
+     * @return the model instance for the event producer (or null if it wasn't found)
+     */
+    public EventProducerModel getProducer(String interfaceName) {
+        return (EventProducerModel)this.producers.get(interfaceName);
+    }
+    
+    /**
+     * Returns the model of an event producer with the given interface.
+     * @param clazz the interface of the event producer
+     * @return the model instance for the event producer (or null if it wasn't found)
+     */
+    public EventProducerModel getProducer(Class clazz) {
+        return getProducer(clazz.getName());
+    }
+    
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        String elName = "event-model";
+        handler.startElement(null, elName, elName, atts);
+        Iterator iter = getProducers();
+        while (iter.hasNext()) {
+            ((XMLizable)iter.next()).toSAX(handler);
+        }
+        handler.endElement(null, elName, elName);
+    }
+
+    private void writeXMLizable(XMLizable object, File outputFile) throws IOException {
+        Result res = new StreamResult(outputFile);
+        
+        try {
+            SAXTransformerFactory tFactory
+                = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+            TransformerHandler handler = tFactory.newTransformerHandler();
+            Transformer transformer = handler.getTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            handler.setResult(res);
+            handler.startDocument();
+            object.toSAX(handler);
+            handler.endDocument();
+        } catch (TransformerConfigurationException e) {
+            throw new IOException(e.getMessage());
+        } catch (TransformerFactoryConfigurationError e) {
+            throw new IOException(e.getMessage());
+        } catch (SAXException e) {
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    /**
+     * Saves this event model to an XML file.
+     * @param modelFile the target file
+     * @throws IOException if an I/O error occurs
+     */
+    public void saveToXML(File modelFile) throws IOException {
+        writeXMLizable(this, modelFile);
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/model/EventModelFactory.java b/src/java/org/apache/fop/events/model/EventModelFactory.java
new file mode 100644 (file)
index 0000000..cd76050
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+/**
+ * This interface is used to instantiate (load, parse) event models.
+ */
+public interface EventModelFactory {
+
+    /**
+     * Creates a new EventModel instance.
+     * @return the new EventModel instance
+     */
+    EventModel createEventModel();
+    
+}
diff --git a/src/java/org/apache/fop/events/model/EventModelParser.java b/src/java/org/apache/fop/events/model/EventModelParser.java
new file mode 100644 (file)
index 0000000..600e495
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.util.Stack;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.DefaultErrorListener;
+
+/**
+ * This is a parser for the event model XML.
+ */
+public class EventModelParser {
+
+    /** Logger instance */
+    protected static Log log = LogFactory.getLog(EventModelParser.class);
+
+    private static SAXTransformerFactory tFactory 
+        = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+    /**
+     * Parses an event model file into an EventModel instance.
+     * @param src the Source instance pointing to the XML file
+     * @return the created event model structure
+     * @throws TransformerException if an error occurs while parsing the XML file
+     */
+    public static EventModel parse(Source src) 
+            throws TransformerException {
+        Transformer transformer = tFactory.newTransformer();
+        transformer.setErrorListener(new DefaultErrorListener(log));
+        
+        EventModel model = new EventModel();
+        SAXResult res = new SAXResult(getContentHandler(model));
+
+        transformer.transform(src, res);
+        return model;
+    }
+
+    /**
+     * Creates a new ContentHandler instance that you can send the event model XML to. The parsed
+     * content is accumulated in the model structure.
+     * @param model the EventModel
+     * @return the ContentHandler instance to receive the SAX stream from the XML file
+     */
+    public static ContentHandler getContentHandler(EventModel model) {
+        return new Handler(model);
+    }
+
+    private static class Handler extends DefaultHandler {
+
+        private EventModel model;
+        private Stack objectStack = new Stack();
+
+        public Handler(EventModel model) {
+            this.model = model;
+        }
+
+        /** {@inheritDoc} */
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                    throws SAXException {
+            try {
+                if ("event-model".equals(localName)) {
+                    if (objectStack.size() > 0) {
+                        throw new SAXException("event-model must be the root element");
+                    }
+                    objectStack.push(model);
+                } else if ("producer".equals(localName)) {
+                    EventProducerModel producer = new EventProducerModel(
+                            attributes.getValue("name"));
+                    EventModel parent = (EventModel)objectStack.peek();
+                    parent.addProducer(producer);
+                    objectStack.push(producer);
+                } else if ("method".equals(localName)) {
+                    EventSeverity severity = EventSeverity.valueOf(attributes.getValue("severity"));
+                    String ex = attributes.getValue("exception");
+                    EventMethodModel method = new EventMethodModel(
+                            attributes.getValue("name"), severity);
+                    if (ex != null && ex.length() > 0) {
+                        method.setExceptionClass(ex);
+                    }
+                    EventProducerModel parent = (EventProducerModel)objectStack.peek();
+                    parent.addMethod(method);
+                    objectStack.push(method);
+                } else if ("parameter".equals(localName)) {
+                    String className = attributes.getValue("type");
+                    Class type;
+                    try {
+                        type = Class.forName(className);
+                    } catch (ClassNotFoundException e) {
+                        throw new SAXException("Could not find Class for: " + className, e);
+                    }
+                    String name = attributes.getValue("name");
+                    EventMethodModel parent = (EventMethodModel)objectStack.peek();
+                    objectStack.push(parent.addParameter(type, name));
+                } else {
+                    throw new SAXException("Invalid element: " + qName);
+                }
+            } catch (ClassCastException cce) {
+                throw new SAXException("XML format error: " + qName, cce);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            objectStack.pop();
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/fop/events/model/EventProducerModel.java b/src/java/org/apache/fop/events/model/EventProducerModel.java
new file mode 100644 (file)
index 0000000..938609c
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * Represents the model of an event producer with multiple event methods.
+ */
+public class EventProducerModel implements Serializable, XMLizable {
+
+    private static final long serialVersionUID = 122267104123721902L;
+    
+    private String interfaceName;
+    private Map methods = new java.util.LinkedHashMap();
+    
+    /**
+     * Creates a new instance.
+     * @param interfaceName the fully qualified interface name of the event producer 
+     */
+    public EventProducerModel(String interfaceName) {
+        this.interfaceName = interfaceName;
+    }
+    
+    /**
+     * Returns the fully qualified interface name of the event producer.
+     * @return the fully qualified interface name
+     */
+    public String getInterfaceName() {
+        return this.interfaceName;
+    }
+    
+    /**
+     * Sets the fully qualified interface name of the event producer.
+     * @param name the fully qualified interface name
+     */
+    public void setInterfaceName(String name) {
+        this.interfaceName = name;
+    }
+    
+    /**
+     * Adds a model instance of an event method.
+     * @param method the event method model
+     */
+    public void addMethod(EventMethodModel method) {
+        this.methods.put(method.getMethodName(), method);
+    }
+    
+    /**
+     * Returns the model instance of an event method for the given method name.
+     * @param methodName the method name
+     * @return the model instance (or null if no method with the given name exists)
+     */
+    public EventMethodModel getMethod(String methodName) {
+        return (EventMethodModel)this.methods.get(methodName);
+    }
+    
+    /**
+     * Returns an iterator over the contained event producer methods.
+     * @return an iterator (Iterator&lt;EventMethodModel&gt;)
+     */
+    public Iterator getMethods() {
+        return this.methods.values().iterator();
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(null, "name", "name", "CDATA", getInterfaceName());
+        String elName = "producer";
+        handler.startElement(null, elName, elName, atts);
+        Iterator iter = getMethods();
+        while (iter.hasNext()) {
+            ((XMLizable)iter.next()).toSAX(handler);
+        }
+        handler.endElement(null, elName, elName);
+    }
+
+    
+}
diff --git a/src/java/org/apache/fop/events/model/EventSeverity.java b/src/java/org/apache/fop/events/model/EventSeverity.java
new file mode 100644 (file)
index 0000000..d37c53c
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events.model;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/** Enumeration class for event severities. */
+public final class EventSeverity implements Serializable {
+
+    private static final long serialVersionUID = 4108175215810759243L;
+    
+    /** info level */
+    public static final EventSeverity INFO = new EventSeverity("INFO");
+    /** warning level */
+    public static final EventSeverity WARN = new EventSeverity("WARN");
+    /** error level */
+    public static final EventSeverity ERROR = new EventSeverity("ERROR");
+    /** fatal error */
+    public static final EventSeverity FATAL = new EventSeverity("FATAL");
+    
+    private String name;
+
+    /**
+     * Constructor to add a new named item.
+     * @param name Name of the item.
+     */
+    private EventSeverity(String name) {
+        this.name = name;
+    }
+
+    /** @return the name of the enumeration */
+    public String getName() {
+        return this.name;
+    }
+    
+    /**
+     * Returns the enumeration/singleton object based on its name.
+     * @param name the name of the enumeration value
+     * @return the enumeration object
+     */
+    public static EventSeverity valueOf(String name) {
+        if (INFO.getName().equalsIgnoreCase(name)) {
+            return INFO;
+        } else if (WARN.getName().equalsIgnoreCase(name)) {
+            return WARN;
+        } else if (ERROR.getName().equalsIgnoreCase(name)) {
+            return ERROR;
+        } else if (FATAL.getName().equalsIgnoreCase(name)) {
+            return FATAL;
+        } else {
+            throw new IllegalArgumentException("Illegal value for enumeration: " + name);
+        }
+    }
+    
+    private Object readResolve() throws ObjectStreamException {
+        return valueOf(getName());
+    }
+    
+    /** {@inheritDoc} */
+    public String toString() {
+        return "EventSeverity:" + name;
+    }
+    
+}
index 0f436ae28322c1197906db158f95e4fbe046b785..a47e6affa0001264c24f21fe1d54f259ec066b74 100644 (file)
@@ -24,9 +24,10 @@ import java.util.HashMap;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.apache.fop.util.QName;
 import org.w3c.dom.DOMImplementation;
 
+import org.apache.xmlgraphics.util.QName;
+
 /**
  * Abstract base class for Element Mappings (including FO Element Mappings)
  * which provide the framework of valid elements and attibutes for a given
index 2ba142203b453b696203a63ed456377af66a71ea..abc4ec8349cdb07924a6fedf2591c8538d2ee8e4 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Iterator;
 import java.util.Map;
 
 import org.w3c.dom.DOMImplementation;
+
 import org.xml.sax.Locator;
 
 import org.apache.commons.logging.Log;
@@ -144,7 +145,6 @@ public class ElementMappingRegistry {
                       + "No element mapping definition found for "
                       + FONode.getNodeString(namespaceURI, localName), locator);
             } else {
-                log.warn("Unknown formatting object " + namespaceURI + "^" + localName);
                 fobjMaker = new UnknownXMLObj.Maker(namespaceURI);
             }
         }
index 51ec85d212910481cd914c837fb71466c1ee2417..62721afebc6df9515b3d260c56c73bc0d02afe3a 100644 (file)
@@ -22,7 +22,7 @@ package org.apache.fop.fo;
 // Java
 import java.util.HashMap;
 
-import org.apache.fop.util.QName;
+import org.apache.xmlgraphics.util.QName;
 
 /**
  * Element mapping class for all XSL-FO elements.
index d226cfb6c2fce29df1cd0acf93fdc39f1ce713f5..5a9a5bb9df36a2cbeecda8068bfc6b8bd5db6197 100644 (file)
@@ -48,6 +48,7 @@ import org.apache.fop.fo.flow.table.TableColumn;
 import org.apache.fop.fo.flow.table.TableRow;
 import org.apache.fop.fo.pagination.Flow;
 import org.apache.fop.fo.pagination.PageSequence;
+import org.apache.fop.fonts.FontEventAdapter;
 import org.apache.fop.fonts.FontInfo;
 
 /**
@@ -101,6 +102,7 @@ public abstract class FOEventHandler {
     public FOEventHandler(FOUserAgent foUserAgent) {
         this.foUserAgent = foUserAgent;
         this.fontInfo = new FontInfo();
+        this.fontInfo.setEventListener(new FontEventAdapter(foUserAgent.getEventBroadcaster()));
     }
 
     /**
index f0422e4140e18f787b451f5a32e315462e48e648..197a2482dec82fdb42b8bc6607ad17fc54d70290 100644 (file)
@@ -21,6 +21,8 @@ package org.apache.fop.fo;
 
 // Java
 import java.util.ListIterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.Locator;
@@ -29,6 +31,8 @@ import org.xml.sax.helpers.LocatorImpl;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
@@ -37,6 +41,7 @@ import org.apache.fop.fo.extensions.svg.SVGElementMapping;
 import org.apache.fop.fo.pagination.Root;
 import org.apache.fop.util.CharUtilities;
 import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.text.AdvancedMessageFormat.Function;
 
 /**
  * Base class for nodes in the XML tree
@@ -143,6 +148,10 @@ public abstract class FONode implements Cloneable {
         return parent.getFOEventHandler();
     }
     
+    /**
+     * Indicates whether this node is a child of an fo:marker.
+     * @return true if this node is a child of an fo:marker
+     */
     protected boolean inMarker() {
         return getFOEventHandler().inMarker();
     }
@@ -239,7 +248,7 @@ public abstract class FONode implements Cloneable {
      * @param start starting array element to add
      * @param end ending array element to add
      * @param pList currently applicable PropertyList 
-     * @param locator location in fo source file.
+     * @param locator location in the XSL-FO source file.
      * @throws FOPException if there's a problem during processing
      */
     protected void addCharacters(char[] data, int start, int end,
@@ -343,54 +352,50 @@ public abstract class FONode implements Cloneable {
     }
 
     /**
-     * Helper function to standardize property error exceptions
-     * (e.g., not specifying either an internal- or an external-destination
-     * property for an FO:link)
-     * @param problem text to display that indicates the problem
-     * @throws ValidationException the validation error provoked by the method call
+     * Returns an instance of the FOValidationEventProducer.
+     * @return an event producer for FO validation
      */
-    protected void attributeError(String problem) 
-                throws ValidationException {
-        throw new ValidationException(errorText(locator) + getName() 
-                + ", " + problem, locator);
+    protected FOValidationEventProducer getFOValidationEventProducer() {
+        return FOValidationEventProducer.Provider.get(
+                getUserAgent().getEventBroadcaster());
     }
-
+    
     /**
-     * Helper function to standardize attribute warnings
-     * (e.g., currently unsupported properties)
-     * @param problem text to display that indicates the problem
+     * Helper function to standardize "too many" error exceptions
+     * (e.g., two fo:declarations within fo:root)
+     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
+     * @param nsURI namespace URI of incoming invalid node
+     * @param lName local name (i.e., no prefix) of incoming node 
+     * @throws ValidationException the validation error provoked by the method call
      */
-    public void attributeWarning(String problem) {
-        log.warn(warningText(locator) + getName() + ", " + problem);
+    protected void tooManyNodesError(Locator loc, String nsURI, String lName) 
+                throws ValidationException {
+        tooManyNodesError(loc, new QName(nsURI, lName));
     }
 
     /**
      * Helper function to standardize "too many" error exceptions
      * (e.g., two fo:declarations within fo:root)
      * @param loc org.xml.sax.Locator object of the error (*not* parent node)
-     * @param nsURI namespace URI of incoming invalid node
-     * @param lName local name (i.e., no prefix) of incoming node 
+     * @param offendingNode the qualified name of the offending node
      * @throws ValidationException the validation error provoked by the method call
      */
-    protected void tooManyNodesError(Locator loc, String nsURI, String lName) 
+    protected void tooManyNodesError(Locator loc, QName offendingNode) 
                 throws ValidationException {
-        throw new ValidationException(errorText(loc) + "For " + getName() 
-            + ", only one " + getNodeString(nsURI, lName) + " may be declared.", 
-            loc);
+        getFOValidationEventProducer().tooManyNodes(this, getName(), offendingNode, loc);
     }
 
     /**
      * Helper function to standardize "too many" error exceptions
      * (e.g., two fo:declarations within fo:root)
-     * This overrloaded method helps make the caller code better self-documenting
+     * This overloaded method helps make the caller code better self-documenting
      * @param loc org.xml.sax.Locator object of the error (*not* parent node)
      * @param offendingNode incoming node that would cause a duplication.
      * @throws ValidationException the validation error provoked by the method call
      */
     protected void tooManyNodesError(Locator loc, String offendingNode) 
                 throws ValidationException {
-        throw new ValidationException(errorText(loc) + "For " + getName() 
-            + ", only one " + offendingNode + " may be declared.", loc);
+        tooManyNodesError(loc, new QName(FO_URI, offendingNode));
     }
 
     /**
@@ -402,9 +407,23 @@ public abstract class FONode implements Cloneable {
      * @throws ValidationException the validation error provoked by the method call
      */
     protected void nodesOutOfOrderError(Locator loc, String tooLateNode, 
-        String tooEarlyNode) throws ValidationException {
-        throw new ValidationException(errorText(loc) + "For " + getName() + ", " + tooLateNode 
-            + " must be declared before " + tooEarlyNode + ".", loc);
+            String tooEarlyNode) throws ValidationException {
+        nodesOutOfOrderError(loc, tooLateNode, tooEarlyNode, false);
+    }
+    
+    /**
+     * Helper function to standardize "out of order" exceptions
+     * (e.g., fo:layout-master-set appearing after fo:page-sequence)
+     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
+     * @param tooLateNode string name of node that should be earlier in document
+     * @param tooEarlyNode string name of node that should be later in document
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @throws ValidationException the validation error provoked by the method call
+     */
+    protected void nodesOutOfOrderError(Locator loc, String tooLateNode, 
+            String tooEarlyNode, boolean canRecover) throws ValidationException {
+        getFOValidationEventProducer().nodeOutOfOrder(this, getName(),
+                tooLateNode, tooEarlyNode, canRecover, loc);
     }
     
     /**
@@ -417,24 +436,24 @@ public abstract class FONode implements Cloneable {
      */
     protected void invalidChildError(Locator loc, String nsURI, String lName) 
                 throws ValidationException {
-        invalidChildError(loc, nsURI, lName, null);
+        invalidChildError(loc, getName(), nsURI, lName, null);
     }
     
     /**
      * Helper function to return "invalid child" exceptions with more
      * complex validation rules (i.e., needing more explanation of the problem)
      * @param loc org.xml.sax.Locator object of the error (*not* parent node)
+     * @param parentName the name of the parent element
      * @param nsURI namespace URI of incoming invalid node
      * @param lName local name (i.e., no prefix) of incoming node
-     * @param ruleViolated text explanation of problem
+     * @param ruleViolated name of the rule violated (used to lookup a resource in a bundle)
      * @throws ValidationException the validation error provoked by the method call
      */
-    protected void invalidChildError(Locator loc, String nsURI, String lName,
+    protected void invalidChildError(Locator loc, String parentName, String nsURI, String lName,
                 String ruleViolated)
                 throws ValidationException {
-        throw new ValidationException(errorText(loc) + getNodeString(nsURI, lName) 
-            + " is not a valid child element of " + getName() 
-            + ((ruleViolated != null) ? ": " + ruleViolated : "."), loc);
+        getFOValidationEventProducer().invalidChild(this, parentName,
+                new QName(nsURI, lName), ruleViolated, loc);
     }
 
     /**
@@ -446,9 +465,22 @@ public abstract class FONode implements Cloneable {
      */
     protected void missingChildElementError(String contentModel)
                 throws ValidationException {
-        throw new ValidationException(errorText(locator) + getName() 
-            + " is missing child elements. \nRequired Content Model: " 
-            + contentModel, locator);
+        getFOValidationEventProducer().missingChildElement(this, getName(),
+                contentModel, false, locator);
+    }
+
+    /**
+     * Helper function to throw an error caused by missing mandatory child elements.
+     * E.g., fo:layout-master-set not having any page-master child element.
+     * @param contentModel The XSL Content Model for the fo: object or a similar description 
+     *                     indicating the necessary child elements.
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @throws ValidationException the validation error provoked by the method call
+     */
+    protected void missingChildElementError(String contentModel, boolean canRecover)
+                throws ValidationException {
+        getFOValidationEventProducer().missingChildElement(this, getName(),
+                contentModel, canRecover, locator);
     }
 
     /**
@@ -458,8 +490,7 @@ public abstract class FONode implements Cloneable {
      */
     protected void missingPropertyError(String propertyName)
                 throws ValidationException {
-        throw new ValidationException(errorText(locator) + getName()
-            + " is missing required \"" + propertyName + "\" property.", locator);
+        getFOValidationEventProducer().missingProperty(this, getName(), propertyName, locator);
     }
 
     /**
@@ -513,9 +544,10 @@ public abstract class FONode implements Cloneable {
     
     /**
      * Returns a String containing as much context information as possible about a node. Call
-     * this methods only in exceptional conditions because this method may perform quite extensive
+     * this method only in exceptional conditions because this method may perform quite extensive
      * information gathering inside the FO tree.
-     * @return a String containing 
+     * @return a String containing context information
+     * @deprecated Not localized! Should rename getContextInfoAlt() to getContextInfo() when done!
      */
     public String getContextInfo() {
         StringBuffer sb = new StringBuffer();
@@ -542,6 +574,54 @@ public abstract class FONode implements Cloneable {
         return sb.toString();
     }
     
+    /**
+     * Returns a String containing as some context information about a node. It does not take the
+     * locator into consideration and returns null if no useful context information can be found.
+     * Call this method only in exceptional conditions because this method may perform quite
+     * extensive information gathering inside the FO tree. All text returned by this method that
+     * is not extracted from document content needs to be locale-independent.
+     * @return a String containing context information
+     */
+    protected String getContextInfoAlt() {
+        String s = gatherContextInfo();
+        if (s != null) {
+            StringBuffer sb = new StringBuffer();
+            if (getLocalName() != null) {
+                sb.append(getName());
+                sb.append(", ");
+            }
+            sb.append("\"");
+            sb.append(s);
+            sb.append("\"");
+            return sb.toString();
+        } else {
+            return null;
+        }
+    }
+    
+    /** Function for AdvancedMessageFormat to retrieve context info from an FONode. */
+    public static class GatherContextInfoFunction implements Function {
+
+        /** {@inheritDoc} */
+        public Object evaluate(Map params) {
+            Object obj = params.get("source");
+            if (obj instanceof PropertyList) {
+                PropertyList propList = (PropertyList)obj;
+                obj = propList.getFObj();
+            }
+            if (obj instanceof FONode) {
+                FONode node = (FONode)obj;
+                return node.getContextInfoAlt();
+            }
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public Object getName() {
+            return "gatherContextInfo";
+        }
+    }
+    
     /**
      * Gathers context information for the getContextInfo() method.
      * @return the collected context information or null, if none is available
@@ -687,7 +767,7 @@ public abstract class FONode implements Cloneable {
          * of child nodes
          * @return  the parent node
          */
-        public FObj parentNode();
+        FObj parentNode();
         
         /**
          * Convenience method with return type of FONode
@@ -695,7 +775,7 @@ public abstract class FONode implements Cloneable {
          * <code>(FONode) next();</code>)
          * @return the next node (if any), as a type FONode
          */
-        public FONode nextNode();
+        FONode nextNode();
         
         /**
          * Convenience method with return type of FONode
@@ -703,7 +783,7 @@ public abstract class FONode implements Cloneable {
          * <code>(FONode) previous();</code>)
          * @return the previous node (if any), as a type FONode
          */
-        public FONode previousNode();
+        FONode previousNode();
         
         /**
          * Returns the first node in the list, and decreases the index,
@@ -711,7 +791,7 @@ public abstract class FONode implements Cloneable {
          * @return the first node in the list
          * @throws NoSuchElementException if the list is empty
          */
-        public FONode firstNode();
+        FONode firstNode();
         
         /**
          * Returns the last node in the list, and advances the
@@ -720,7 +800,7 @@ public abstract class FONode implements Cloneable {
          * @return the last node in the list
          * @throws NoSuchElementException if the list is empty
          */
-        public FONode lastNode();
+        FONode lastNode();
 
     }
 }
index 6d1ac417fc3b1e87a977f472d6f86a30edb2cc28..99d37dba99ddc0268cb906d6fafc9c30cfadef54 100644 (file)
 
 package org.apache.fop.fo;
 
-// Java
 import java.awt.Color;
 import java.util.NoSuchElementException;
 
-// FOP
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.flow.Block;
@@ -34,9 +34,6 @@ import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.SpaceProperty;
 
-// SAX
-import org.xml.sax.Locator;
-
 /**
  * A text node (PCDATA) in the formatting object tree.
  *
@@ -396,7 +393,7 @@ public class FOText extends FONode {
                 return ca[i];
             }
         default:
-            log.warn("Invalid text-tranform value: " + textTransform);
+            assert false; //should never happen as the property subsystem catches that case
             return ca[i];
         }
     }
index d02a058fe8039e01d33d07c4c7df9a22f3ad06aa..84abc4b8b48493c3d8aa280917dce2fd33b6bf79 100644 (file)
@@ -31,6 +31,8 @@ import org.xml.sax.helpers.DefaultHandler;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.FormattingResults;
@@ -130,6 +132,7 @@ public class FOTreeBuilder extends DefaultHandler {
             throw new IllegalStateException("FOTreeBuilder (and the Fop class) cannot be reused."
                     + " Please instantiate a new instance.");
         }
+        
         used = true;
         empty = true;
         rootFObj = null;    // allows FOTreeBuilder to be reused
@@ -146,8 +149,10 @@ public class FOTreeBuilder extends DefaultHandler {
     public void endDocument() throws SAXException {
         this.delegate.endDocument();
         if (this.rootFObj == null && empty) {
-            throw new ValidationException(
-                    "Document is empty (something might be wrong with your XSLT stylesheet).");
+            FOValidationEventProducer eventProducer
+                = FOValidationEventProducer.Provider.get(
+                    foEventHandler.getUserAgent().getEventBroadcaster());
+            eventProducer.emptyDocument(this);
         }
         rootFObj = null;
         if (log.isDebugEnabled()) {
@@ -178,18 +183,6 @@ public class FOTreeBuilder extends DefaultHandler {
         }
     }
 
-    /**
-     * Finds the {@link Maker} used to create {@link FONode} objects of a particular type
-     * 
-     * @param namespaceURI URI for the namespace of the element
-     * @param localName name of the Element
-     * @return the ElementMapping.Maker that can create an FO object for this element
-     * @throws FOPException if a Maker could not be found for a bound namespace.
-     */
-    private Maker findFOMaker(String namespaceURI, String localName) throws FOPException {
-        return elementMappingRegistry.findFOMaker(namespaceURI, localName, locator);
-    }
-
     /** {@inheritDoc} */
     public void warning(SAXParseException e) {
         log.warn(e.getLocalizedMessage());
@@ -258,22 +251,21 @@ public class FOTreeBuilder extends DefaultHandler {
             if (rootFObj == null) {
                 empty = false;
                 if (!namespaceURI.equals(FOElementMapping.URI) 
-                    || !localName.equals("root")) {
-                    throw new ValidationException(
-                        "Error: First element must be the fo:root formatting object. "
-                        + "Found " + FONode.getNodeString(namespaceURI, localName) 
-                        + " instead."
-                        + " Please make sure you're producing a valid XSL-FO document.");
+                        || !localName.equals("root")) {
+                    FOValidationEventProducer eventProducer
+                        = FOValidationEventProducer.Provider.get(
+                                foEventHandler.getUserAgent().getEventBroadcaster());
+                    eventProducer.invalidFORoot(this, FONode.getNodeString(namespaceURI, localName),
+                            getEffectiveLocator());
                 }
             } else { // check that incoming node is valid for currentFObj
-                if (namespaceURI.equals(FOElementMapping.URI)
-                    || namespaceURI.equals(ExtensionElementMapping.URI)) {
+                if (currentFObj.getNamespaceURI().equals(FOElementMapping.URI)
+                    || currentFObj.getNamespaceURI().equals(ExtensionElementMapping.URI)) {
                     currentFObj.validateChildNode(locator, namespaceURI, localName);
                 }
             }
             
-            ElementMapping.Maker fobjMaker = 
-                findFOMaker(namespaceURI, localName);
+            ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName);
 
             try {
                 foNode = fobjMaker.make(currentFObj);
@@ -342,8 +334,7 @@ public class FOTreeBuilder extends DefaultHandler {
             if (currentPropertyList != null
                     && currentPropertyList.getFObj() == currentFObj
                     && !foEventHandler.inMarker()) {
-                currentPropertyList = 
-                    currentPropertyList.getParentPropertyList();
+                currentPropertyList = currentPropertyList.getParentPropertyList();
             }
             
             if (currentFObj.getNameId() == Constants.FO_MARKER) {
@@ -373,7 +364,29 @@ public class FOTreeBuilder extends DefaultHandler {
         /** {@inheritDoc} */
         public void endDocument() throws SAXException {
             currentFObj = null;
-        }        
+        }
+        
+        /**
+         * Finds the {@link Maker} used to create {@link FONode} objects of a particular type
+         * 
+         * @param namespaceURI URI for the namespace of the element
+         * @param localName name of the Element
+         * @return the ElementMapping.Maker that can create an FO object for this element
+         * @throws FOPException if a Maker could not be found for a bound namespace.
+         */
+        private Maker findFOMaker(String namespaceURI, String localName) throws FOPException {
+            Maker maker = elementMappingRegistry.findFOMaker(namespaceURI, localName, locator);
+            if (maker instanceof UnknownXMLObj.Maker) {
+                FOValidationEventProducer eventProducer
+                    = FOValidationEventProducer.Provider.get(
+                        foEventHandler.getUserAgent().getEventBroadcaster());
+                eventProducer.unknownFormattingObject(this, currentFObj.getName(),
+                        new QName(namespaceURI, localName),
+                        getEffectiveLocator());
+            }
+            return maker;
+        }
+
     }
 }
 
diff --git a/src/java/org/apache/fop/fo/FOValidationEventProducer.java b/src/java/org/apache/fop/fo/FOValidationEventProducer.java
new file mode 100644 (file)
index 0000000..aa7b149
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fo;
+
+import org.xml.sax.Locator;
+
+import org.apache.xmlgraphics.util.QName;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.fo.expr.PropertyException;
+
+/**
+ * Event producer interface for XSL-FO validation messages.
+ */
+public interface FOValidationEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static FOValidationEventProducer get(EventBroadcaster broadcaster) {
+            return (FOValidationEventProducer)broadcaster.getEventProducerFor(
+                    FOValidationEventProducer.class);
+        }
+    }
+
+    /**
+     * Too many child nodes.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param offendingNode the offending node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call 
+     * @event.severity FATAL
+     */
+    void tooManyNodes(Object source, String elementName, QName offendingNode,
+            Locator loc) throws ValidationException;
+    
+    /**
+     * The node order is wrong.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param tooLateNode string name of node that should be earlier in document
+     * @param tooEarlyNode string name of node that should be later in document
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     */
+    void nodeOutOfOrder(Object source, String elementName,
+            String tooLateNode, String tooEarlyNode, boolean canRecover,
+            Locator loc) throws ValidationException;
+    
+    /**
+     * An invalid child was encountered.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param offendingNode the offending node
+     * @param ruleViolated the rule that was violated or null
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     */
+    void invalidChild(Object source, String elementName, QName offendingNode, String ruleViolated,
+            Locator loc) throws ValidationException;
+
+    /**
+     * A required child element is missing.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param contentModel the expected content model
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void missingChildElement(Object source, String elementName,
+            String contentModel, boolean canRecover,
+            Locator loc) throws ValidationException;
+
+    /**
+     * An element is missing a required property.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param propertyName the name of the missing property
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void missingProperty(Object source, String elementName, String propertyName,
+            Locator loc) throws ValidationException;
+    
+    /**
+     * An id was used twice in a document.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param id the id that was reused
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void idNotUnique(Object source, String elementName, String id, boolean canRecover,
+            Locator loc) throws ValidationException;
+
+    /**
+     * There are multiple color profiles defined with the same name.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param name the duplicate color profile name
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void colorProfileNameNotUnique(Object source, String elementName, String name,
+            Locator loc);
+
+    /**
+     * There are multiple page masters defined with the same name.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param name the duplicate page master name
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void masterNameNotUnique(Object source, String elementName, String name,
+            Locator loc) throws ValidationException;
+
+    /**
+     * A marker is not an initial child on a node.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param mcname the marker class name
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void markerNotInitialChild(Object source, String elementName, String mcname, Locator loc);
+
+    /**
+     * A marker class name is not unique within the same parent.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param mcname the marker class name
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void markerNotUniqueForSameParent(Object source, String elementName,
+            String mcname, Locator loc);
+
+    /**
+     * An invalid property was found.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param attr the invalid attribute
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void invalidProperty(Object source, String elementName, QName attr, boolean canRecover,
+            Locator loc) throws ValidationException;
+
+    /**
+     * An invalid property value was encountered.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param propName the property name
+     * @param propValue the property value
+     * @param e the property exception caused by the invalid value
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void invalidPropertyValue(Object source, String elementName,
+            String propName, String propValue, PropertyException e,
+            Locator loc);
+
+    /**
+     * A feature is not supported, yet.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param feature the unsupported feature
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void unimplementedFeature(Object source, String elementName, String feature,
+            Locator loc);
+
+    /**
+     * Missing internal-/external-destination on basic-link or bookmark.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void missingLinkDestination(Object source, String elementName, Locator loc)
+                throws ValidationException;
+
+    /**
+     * Indicates a problem while cloning a marker (ex. due to invalid property values).
+     * @param source the event source
+     * @param markerClassName the "marker-class-name" of the marker
+     * @param fe the FOP exception that cause this problem
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void markerCloningFailed(Object source, String markerClassName, FOPException fe, Locator loc);
+
+    /**
+     * A region name is mapped to multiple region classes.
+     * @param source the event source
+     * @param regionName the region name
+     * @param defaultRegionClass1 the first default region class
+     * @param defaultRegionClass2 the second default region class
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void regionNameMappedToMultipleRegionClasses(Object source, String regionName,
+            String defaultRegionClass1, String defaultRegionClass2, Locator loc)
+                throws ValidationException;
+
+    /**
+     * There are multiple flows with the same name.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param flowName the flow name
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void duplicateFlowNameInPageSequence(Object source, String elementName, String flowName,
+            Locator loc) throws ValidationException;
+
+    /**
+     * A flow name could not be mapped to a region.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param flowName the flow name
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void flowNameNotMapped(Object source, String elementName, String flowName,
+            Locator loc) throws ValidationException;
+
+    /**
+     * A page master could not be found.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param masterReference the page master reference
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void masterNotFound(Object source, String elementName, String masterReference,
+            Locator loc) throws ValidationException;
+
+    /**
+     * An illegal region name was used.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param regionName the region name
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void illegalRegionName(Object source, String elementName, String regionName,
+            Locator loc) throws ValidationException;
+
+    /**
+     * A non-zero border and/or padding has been encountered on a region.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param regionName the region name
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void nonZeroBorderPaddingOnRegion(Object source, String elementName, String regionName,
+            boolean canRecover, Locator loc) throws ValidationException;
+
+    /**
+     * If overflow property is set to "scroll", a column-count other than "1" may not be specified.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void columnCountErrorOnRegionBodyOverflowScroll(Object source, String elementName,
+            Locator loc) throws ValidationException;
+
+    /**
+     * fo:root must be root.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void invalidFORoot(Object source, String elementName,
+            Locator loc) throws ValidationException;
+    
+    /**
+     * No FO document was found.
+     * @param source the event source
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void emptyDocument(Object source) throws ValidationException;
+    /**
+     * An unknown/unsupported formatting object has been encountered.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param offendingNode the offending node
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void unknownFormattingObject(Object source, String elementName,
+            QName offendingNode, Locator loc);
+    
+}
index b2587df2dc47b1143f7e24b858f4de3e4ef25c45..a03a351e0c56a9f06fdb396361f4c3bce753b1e6 100644 (file)
@@ -27,13 +27,15 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fo.flow.Marker;
 import org.apache.fop.fo.properties.PropertyMaker;
-import org.apache.fop.util.QName;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
 
 /**
  * Base class for representation of formatting objects and their processing.
@@ -171,25 +173,7 @@ public abstract class FObj extends FONode implements Constants {
             if (!idrefs.contains(id)) {
                 idrefs.add(id);
             } else {
-                if (getUserAgent().validateStrictly()) {
-                    throw new ValidationException("Property id \"" + id 
-                            + "\" previously used; id values must be unique"
-                            + " in document.", locator);
-                } else {
-                    if (log.isWarnEnabled()) {
-                        StringBuffer msg = new StringBuffer();
-                        msg.append("Found non-unique id on ").append(getName());
-                        if (locator.getLineNumber() != -1) {
-                            msg.append(" (at ").append(locator.getLineNumber())
-                                .append("/").append(locator.getColumnNumber())
-                                .append(")");
-                        }
-                        msg.append("\nAny reference to it will be considered "
-                                + "a reference to the first occurrence "
-                                + "in the document.");
-                        log.warn(msg);
-                    }
-                }
+                getFOValidationEventProducer().idNotUnique(this, getName(), id, true, locator);
             }
         }
     }
@@ -283,16 +267,22 @@ public abstract class FObj extends FONode implements Constants {
         return false;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public FONodeIterator getChildNodes() {
-        if (firstChild != null) {
+        if (hasChildren()) {
             return new FObjIterator(this);
         }
         return null;
     }
 
+    /**
+     * Indicates whether this formatting object has children.
+     * @return true if there are children
+     */
+    public boolean hasChildren() {
+        return this.firstChild != null;
+    }
+    
     /**
      * Return an iterator over the object's childNodes starting
      * at the passed-in node (= first call to iterator.next() will
@@ -348,8 +338,8 @@ public abstract class FObj extends FONode implements Constants {
                 if (node instanceof FObj
                         || (node instanceof FOText
                                 && ((FOText) node).willCreateArea())) {
-                    log.error(
-                            "fo:marker must be an initial child: " + mcname);
+                    getFOValidationEventProducer().markerNotInitialChild(this, getName(),
+                            mcname, locator);
                     return;
                 } else if (node instanceof FOText) {
                     iter.remove();
@@ -363,8 +353,8 @@ public abstract class FObj extends FONode implements Constants {
         if (!markers.containsKey(mcname)) {
             markers.put(mcname, marker);
         } else {
-            log.error("fo:marker 'marker-class-name' "
-                    + "must be unique for same parent: " + mcname);
+            getFOValidationEventProducer().markerNotUniqueForSameParent(this, getName(),
+                    mcname, locator);
         }
     }
 
@@ -382,6 +372,33 @@ public abstract class FObj extends FONode implements Constants {
         return markers;
     }
 
+    /** {@inheritDoc} */
+    protected String getContextInfoAlt() {
+        StringBuffer sb = new StringBuffer();
+        if (getLocalName() != null) {
+            sb.append(getName());
+            sb.append(", ");
+        }
+        if (hasId()) {
+            sb.append("id=").append(getId());
+            return sb.toString();
+        }
+        String s = gatherContextInfo();
+        if (s != null) {
+            sb.append("\"");
+            if (s.length() < 32) {
+                sb.append(s);
+            } else {
+                sb.append(s.substring(0, 32));
+                sb.append("...");
+            }
+            sb.append("\"");
+            return sb.toString();
+        } else {
+            return null;
+        }
+    }
+    
     /** {@inheritDoc} */
     protected String gatherContextInfo() {
         if (getLocator() != null) {
index 3d050efed46724548ee6a6065f57eec69cfed6fa..b6766bfe9b335b196a40fe639b6b52d1e3d6c4b3 100644 (file)
 package org.apache.fop.fo;
 
 // Java
-import java.text.MessageFormat;
-
 import org.xml.sax.Attributes;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FopFactory;
 import org.apache.fop.fo.expr.PropertyException;
 import org.apache.fop.fo.properties.CommonAbsolutePosition;
@@ -41,7 +41,6 @@ import org.apache.fop.fo.properties.CommonRelativePosition;
 import org.apache.fop.fo.properties.CommonTextDecoration;
 import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.PropertyMaker;
-import org.apache.fop.util.QName;
 
 /**
  * Class containing the collection of properties for a given FObj.
@@ -150,7 +149,7 @@ public abstract class PropertyList {
      * the default value.
      * @param propId The Constants ID of the property whose value is desired.
      * @return the Property corresponding to that name
-     * @throws PropertyException ...
+     * @throws PropertyException if there is a problem evaluating the property 
      */
     public Property get(int propId) throws PropertyException {
         return get(propId, true, true);
@@ -166,7 +165,7 @@ public abstract class PropertyList {
      *                      value is needed
      * @param bTryDefault   true when the default value may be used as a last resort
      * @return the property
-     * @throws PropertyException ...
+     * @throws PropertyException if there is a problem evaluating the property 
      */
     public Property get(int propId, boolean bTryInherit,
                          boolean bTryDefault) throws PropertyException {
@@ -321,20 +320,18 @@ public abstract class PropertyList {
             } else if (!factory.isNamespaceIgnored(attributeNS)) {
                 ElementMapping mapping = factory.getElementMappingRegistry().getElementMapping(
                         attributeNS);
+                QName attr = new QName(attributeNS, attributeName);
                 if (mapping != null) {
-                    QName attName = new QName(attributeNS, attributeName);
-                    if (mapping.isAttributeProperty(attName) 
+                    if (mapping.isAttributeProperty(attr) 
                             && mapping.getStandardPrefix() != null) {
                         convertAttributeToProperty(attributes, 
-                                mapping.getStandardPrefix() + ":" + attName.getLocalName(), 
+                                mapping.getStandardPrefix() + ":" + attr.getLocalName(), 
                                 attributeValue);
                     } else {
-                        getFObj().addForeignAttribute(attName, attributeValue);
+                        getFObj().addForeignAttribute(attr, attributeValue);
                     }
                 } else {
-                    handleInvalidProperty(
-                            "Error processing foreign attribute: "
-                            + attributeNS + "/@" + attributeName, attributeName);
+                    handleInvalidProperty(attr);
                 }
             }
         }
@@ -345,11 +342,8 @@ public abstract class PropertyList {
      * @param propertyName  the property name to check
      * @return true if the base property name and the subproperty name (if any)
      *           can be correctly mapped to an id
-     * @throws ValidationException in case the property name
-     *          is invalid for the FO namespace
      */
-    protected boolean isValidPropertyName(String propertyName) 
-                throws ValidationException {
+    protected boolean isValidPropertyName(String propertyName) {
 
         int propId = FOPropertyMapping.getPropertyId(
                         findBasePropertyName(propertyName));
@@ -359,9 +353,6 @@ public abstract class PropertyList {
         if (propId == -1 
                 || (subpropId == -1 
                         && findSubPropertyName(propertyName) != null)) {
-            String errorMessage = MessageFormat.format(
-                    "Invalid property name ''{0}''.", new Object[] {propertyName});
-            handleInvalidProperty(errorMessage, propertyName);
             return false;
         }
         return true;
@@ -382,19 +373,23 @@ public abstract class PropertyList {
         
         if (attributeValue != null) {
 
-            if (!isValidPropertyName(attributeName)) {
-                //will log an error or throw an exception
+            if (attributeName.startsWith("xmlns:")) {
+                //Ignore namespace declarations
                 return;
             }
-            FObj parentFO = fobj.findNearestAncestorFObj();
             
-    
             /* Handle "compound" properties, ex. space-before.minimum */
             String basePropertyName = findBasePropertyName(attributeName);
             String subPropertyName = findSubPropertyName(attributeName);
 
             int propId = FOPropertyMapping.getPropertyId(basePropertyName);
             int subpropId = FOPropertyMapping.getSubPropertyId(subPropertyName);
+            
+            if (propId == -1 
+                    || (subpropId == -1 && subPropertyName != null)) {
+                handleInvalidProperty(new QName(null, attributeName));
+            }
+            FObj parentFO = fobj.findNearestAncestorFObj();
     
             PropertyMaker propertyMaker = findMaker(propId);
             if (propertyMaker == null) {
@@ -417,8 +412,8 @@ public abstract class PropertyList {
                     }
                     prop = propertyMaker.make(this, attributeValue, parentFO);
                 } else { // e.g. "leader-length.maximum"
-                    Property baseProperty = 
-                        findBaseProperty(attributes, parentFO, propId, 
+                    Property baseProperty
+                        findBaseProperty(attributes, parentFO, propId, 
                                 basePropertyName, propertyMaker);
                     prop = propertyMaker.make(baseProperty, subpropId,
                             this, attributeValue, parentFO);
@@ -427,8 +422,8 @@ public abstract class PropertyList {
                     putExplicit(propId, prop);
                 }
             } catch (PropertyException e) {
-                log.error("Ignoring property: " 
-                        + attributeName + "=\"" + attributeValue + "\" (" + e.getMessage() + ")");
+                fobj.getFOValidationEventProducer().invalidPropertyValue(this, fobj.getName(),
+                        attributeName, attributeValue, e, fobj.locator);
             }
         }
     }
@@ -465,18 +460,16 @@ public abstract class PropertyList {
     }
 
     /**
-     * @param message ...
-     * @param propName ...
-     * @throws ValidationException ...
+     * Handles an invalid property.
+     * @param attr the invalid attribute
+     * @throws ValidationException if an exception needs to be thrown depending on the
+     *                  validation settings
      */
-    protected void handleInvalidProperty(String message, String propName
+    protected void handleInvalidProperty(QName attr
                     throws ValidationException {
-        if (!propName.startsWith("xmlns")) {
-            if (fobj.getUserAgent().validateStrictly()) {
-                fobj.attributeError(message);
-            } else {
-                log.error(message + " Property ignored.");
-            }
+        if (!attr.getQName().startsWith("xmlns")) {
+            fobj.getFOValidationEventProducer().invalidProperty(this, fobj.getName(),
+                    attr, true, fobj.locator);
         }
     }
 
index b5a82de569d4b39e2c8e0ec13af9c543d8be50df..b09d3c95ff9d98422344a0c964e449a70dd42e7f 100644 (file)
@@ -64,7 +64,13 @@ public class FromParentFunction extends FunctionBase {
          * non-inherited properties too. Perhaps the result is different for
          * a property line line-height which "inherits specified"???
          */
-        return pInfo.getPropertyList().getFromParent(FOPropertyMapping.getPropertyId(propName));
+        int propId = FOPropertyMapping.getPropertyId(propName);
+        if (propId < 0) {
+            throw new PropertyException(
+                    "Unknown property name used with inherited-property-value function: "
+                        + propName);
+        }
+        return pInfo.getPropertyList().getFromParent(propId);
     }
 
 }
index 3e5cadf04fef46eaa7d7520d5b1bbb72ad4b8707..e24c78caa0d64da8eb74508a0c45890f24a95f9a 100644 (file)
@@ -58,6 +58,11 @@ public class InheritedPropFunction extends FunctionBase {
         }
 
         int propId = FOPropertyMapping.getPropertyId(propName);
+        if (propId < 0) {
+            throw new PropertyException(
+                    "Unknown property name used with inherited-property-value function: "
+                        + propName);
+        }
         return pInfo.getPropertyList().getInherited(propId);
     }
 
index 2aab5eee92c7aab4cd4bd44f2a884357cdf65597..cdde960925078da71c6c9a5049c203fd521c3277 100644 (file)
@@ -60,6 +60,11 @@ public class NearestSpecPropFunction extends FunctionBase {
         // NOTE: special cases for shorthand property
         // Should return COMPUTED VALUE
         int propId = FOPropertyMapping.getPropertyId(propName);
+        if (propId < 0) {
+            throw new PropertyException(
+                    "Unknown property name used with inherited-property-value function: "
+                        + propName);
+        }
         return pInfo.getPropertyList().getNearestSpecified(propId);
     }
 
index de1d019f4c6bee77aa6b11eb223b78a3ba23a3a7..fc61167b2613c941d24f7e941b3385bb36d92e07 100644 (file)
 
 package org.apache.fop.fo.extensions;
 
+import java.util.HashMap;
+import java.util.Set;
+
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.fo.ElementMapping;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.UnknownXMLObj;
 import org.apache.fop.fo.extensions.destination.Destination;
-import org.apache.fop.util.QName;
-
-import java.util.HashMap;
-import java.util.Set;
 
 /**
  * Element mapping for FOP's proprietary extension to XSL-FO.
index d1e631e42532f561e3d5c5b2a58638411ea7e331..e3a2bbac47ae7c5c6f8eb6062e7836312fce0588 100644 (file)
 
 package org.apache.fop.fo.extensions.destination;
 
-import org.apache.fop.fo.ValidationException;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
-import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.pagination.Root;
+import org.apache.fop.fo.PropertyList;
+import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
+import org.apache.fop.fo.pagination.Root;
 
 /**
  * Class for named destinations in PDF.
@@ -54,7 +54,7 @@ public class Destination extends FONode {
             Attributes attlist, PropertyList pList) throws FOPException {
         internalDestination = attlist.getValue("internal-destination");
         if (internalDestination == null || internalDestination.length() == 0) {
-            attributeError("Missing attribute:  internal-destination must be specified.");
+            missingPropertyError("internal-destination");
         }
     }
     
index 0fe6ed718709e9517f13ce751dd0adfddf4e3115..f99f9d94745bb10efa5dcfddcb958bd06611dff1 100644 (file)
@@ -62,14 +62,16 @@ public abstract class AbstractListItemPart extends FObj {
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
         throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
@@ -79,17 +81,8 @@ public abstract class AbstractListItemPart extends FObj {
     protected void endOfNode() throws FOPException {
         if (!this.blockItemFound) {
             String contentModel = "marker* (%block;)+";
-            if (getUserAgent().validateStrictly()) {
-                missingChildElementError(contentModel);
-            } else {
-                StringBuffer message = new StringBuffer(
-                        errorText(getLocator()));
-                message.append(getName())
-                    .append(" is missing child elements. ")
-                    .append("Required Content Model: ")
-                    .append(contentModel);
-                log.warn(message.toString());
-            }
+            getFOValidationEventProducer().missingChildElement(this, getName(),
+                    contentModel, true, getLocator());
         }
     }
 
index b6b8272487cba74dbe7e3ca857f0a44b860144ea..e9a1176d6dae3bfa35cee10f85d0696e21e6e57c 100644 (file)
@@ -113,8 +113,10 @@ public abstract class AbstractPageNumberCitation extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /** @return the Common Font Properties. */
index 61a4f8d1922b858a9d9fe37a46ecdc2fd5b7404f..b3ef48012fda1cf1b59c9b180d6f2bb921f10e62 100644 (file)
@@ -76,8 +76,7 @@ public class BasicLink extends Inline {
             externalDestination = null;
         } else if (externalDestination.length() == 0) {
             // slightly stronger than spec "should be specified"
-            attributeError("Missing attribute:  Either external-destination or " +
-                "internal-destination must be specified.");
+            getFOValidationEventProducer().missingLinkDestination(this, getName(), locator);
         }
     }
 
@@ -102,15 +101,17 @@ public class BasicLink extends Inline {
      * XSL Content Model: marker* (#PCDATA|%inline;|%block;)*
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockOrInlineItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(#PCDATA|%inline;|%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockOrInlineItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(#PCDATA|%inline;|%block;)");
+                }
+            } else if (!isBlockOrInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockOrInlineItemFound = true;
             }
-        } else if (!isBlockOrInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockOrInlineItemFound = true;
         }
     }
 
index 2157085ad9cac295e9246ab1b97214d8c17cad69..892f4a3c5b70cdef5b88d9adfb47d1bc0dd2354b 100644 (file)
 
 package org.apache.fop.fo.flow;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObjMixed;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:bidi-override object.
@@ -96,22 +97,21 @@ public class BidiOverride extends FObjMixed {
      *  fo:inline-container."
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockOrInlineItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", 
-                    "(#PCDATA|%inline;|%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockOrInlineItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", 
+                        "(#PCDATA|%inline;|%block;)");
+                }
+            } else if (!isBlockOrInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, getParent().getName(), nsURI, getName(),
+                        "rule.bidiOverrideContent");
+            } else {
+                blockOrInlineItemFound = true;
             }
-        } else if (!isBlockOrInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
-            String ruleViolated = "An fo:bidi-override"
-                + " that is a descendant of an fo:leader or of the fo:inline child"
-                + " of an fo:footnote may not have block-level children, unless it" 
-                + " has a nearer ancestor that is an fo:inline-container.";
-            invalidChildError(loc, nsURI, localName, ruleViolated);
-        } else {
-            blockOrInlineItemFound = true;
         }
     }
 
index a7199993812dd415193df9201d327c9fb290775f..f1180ac162dbaefeb4b13dc36b5c384c442619df 100644 (file)
@@ -114,15 +114,17 @@ public class BlockContainer extends FObj {
      * @todo - implement above restriction if possible
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
index 022d54af70b3dea64a8b03b385a57d536fc74672..aad4209f9150c10b3617eeb2383df33cd82594ee 100644 (file)
@@ -22,6 +22,8 @@ package org.apache.fop.fo.flow;
 import java.awt.Color;
 import java.util.NoSuchElementException;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.CharIterator;
@@ -35,7 +37,6 @@ import org.apache.fop.fo.properties.CommonHyphenation;
 import org.apache.fop.fo.properties.CommonTextDecoration;
 import org.apache.fop.fo.properties.Property;
 import org.apache.fop.fo.properties.SpaceProperty;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:character object. 
@@ -134,8 +135,10 @@ public class Character extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
index fdddf3918203dcbccdc3f7258459ced0b8247a68..07f765e5287c3370653dc2e46efb7b0b3291f4a0 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
@@ -78,11 +79,17 @@ public class ExternalGraphic extends AbstractGraphics {
         try {
             info = manager.getImageInfo(url, userAgent.getImageSessionContext());
         } catch (ImageException e) {
-            log.error("Image not available: " + e.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, url, e, getLocator());
         } catch (FileNotFoundException fnfe) {
-            log.error(fnfe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, url, fnfe, getLocator());
         } catch (IOException ioe) {
-            log.error("I/O error while loading image: " + ioe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, url, ioe, getLocator());
         }
         if (info != null) {
             this.intrinsicWidth = info.getSize().getWidthMpt();
@@ -93,7 +100,6 @@ public class ExternalGraphic extends AbstractGraphics {
                     = FixedLength.getInstance(-baseline);
             }
         }
-        //TODO Report to caller so he can decide to throw an exception
     }
 
     /** {@inheritDoc} */
@@ -107,8 +113,10 @@ public class ExternalGraphic extends AbstractGraphics {
      * <br>XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /** @return the "src" property */
index 997f96c89b0476390e55be85220477e874dcc782..57d3b4ee18f121185e46c97d343042e7cb902b96 100644 (file)
@@ -46,7 +46,8 @@ public class Float extends FObj {
         super(parent);
         
         if (!notImplementedWarningGiven) {
-            log.warn("fo:float is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -63,10 +64,12 @@ public class Float extends FObj {
      * XSL Content Model: (%block;)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             if (!isBlockItem(nsURI, localName)) {
                 invalidChildError(loc, nsURI, localName);
             }
+        }
     }
 
     /**
index 95c9f25bc2e7aaf760c32d8cb6008d01eca8b2b4..c15a7e0eeee9c2a4565274f7eba485b72157c9cc 100644 (file)
@@ -82,12 +82,13 @@ public class Footnote extends FObj {
      *      generates an absolutely positioned area.
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-            if (FO_URI.equals(nsURI) && localName.equals("inline")) {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("inline")) {
                 if (footnoteCitation != null) {
                     tooManyNodesError(loc, "fo:inline");
                 }
-            } else if (FO_URI.equals(nsURI) && localName.equals("footnote-body")) {
+            } else if (localName.equals("footnote-body")) {
                 if (footnoteCitation == null) {
                     nodesOutOfOrderError(loc, "fo:inline", "fo:footnote-body");
                 } else if (footnoteBody != null) {
@@ -96,6 +97,7 @@ public class Footnote extends FObj {
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
+        }
     }
 
     /**
index bb4c9b48271c39c6af6aad81c5a7c0a1b1273ef9..967d152155a0d67876d3bf8453870ab7d225c2f5 100644 (file)
@@ -73,10 +73,12 @@ public class FootnoteBody extends FObj {
      * XSL Content Model: (%block;)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             if (!isBlockItem(nsURI, localName)) {
                 invalidChildError(loc, nsURI, localName);
             }
+        }
     }
 
     /** {@inheritDoc} */
index 63299978d8eba8e7ba9826768168a580a017d7c6..6d0e495b775fd322adc5e018b8bdfba919bbd38c 100644 (file)
@@ -72,8 +72,10 @@ public class InitialPropertySet extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
index 6bbd90ad184f66d77139d1a70ec733fa6da36c55..50662d9f129be9377ecc67c9e6abdf158845341c 100644 (file)
@@ -81,8 +81,8 @@ public class Inline extends InlineLevel {
        int lvlInCntr = findAncestor(FO_INLINE_CONTAINER);
 
        if (lvlLeader > 0) {
-           if (lvlInCntr < 0 ||
-               (lvlInCntr > 0 && lvlInCntr > lvlLeader)) {
+           if (lvlInCntr < 0
+               || (lvlInCntr > 0 && lvlInCntr > lvlLeader)) {
                canHaveBlockLevelChildren = false;
            }
        } else if (lvlFootnote > 0) {
@@ -110,23 +110,20 @@ public class Inline extends InlineLevel {
      *  nearer ancestor that is an fo:inline-container." (paraphrased)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockOrInlineItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", 
-                    "(#PCDATA|%inline;|%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockOrInlineItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", 
+                        "(#PCDATA|%inline;|%block;)");
+                }
+            } else if (!isBlockOrInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, getParent().getName(), nsURI, getName(), "rule.inlineContent");
+            } else {
+                blockOrInlineItemFound = true;
             }
-        } else if (!isBlockOrInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
-            String ruleViolated = 
-                " An fo:inline that is a descendant of an fo:leader" +
-                " or fo:footnote may not have block-level children," +
-                " unless it has a nearer ancestor that is an" +
-                " fo:inline-container.";
-            invalidChildError(loc, nsURI, localName, ruleViolated);
-        } else {
-            blockOrInlineItemFound = true;
         }
     }
 
index a8fb7858d504d2e72c4867d18ceefef940fab675..3c142afe9d969c0e6c69328c4a47737b8fed3e5c 100644 (file)
@@ -86,15 +86,17 @@ public class InlineContainer extends FObj {
      * XSL Content Model: marker* (%block;)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
index 531bd657a1c3aac75ad2766415a2cbc9da9e5e44..802f59c30f0c879903494d7a15642b4917c49c8a 100644 (file)
 package org.apache.fop.fo.flow;
 
 import java.awt.geom.Point2D;
+
+import org.xml.sax.Locator;
+
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.XMLObj;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:instream-foreign-object object.
@@ -39,6 +44,7 @@ public class InstreamForeignObject extends AbstractGraphics {
 
     //Additional value
     private Point2D intrinsicDimensions;
+    private boolean instrisicSizeDetermined;
     
     private Length intrinsicAlignmentAdjust;
     
@@ -68,11 +74,11 @@ public class InstreamForeignObject extends AbstractGraphics {
      * XSL Content Model: one (1) non-XSL namespace child
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
         if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
         } else if (firstChild != null) {
-            tooManyNodesError(loc, "child element");
+            tooManyNodesError(loc, new QName(nsURI, null, localName));
         }
     }
 
@@ -81,32 +87,28 @@ public class InstreamForeignObject extends AbstractGraphics {
         return "instream-foreign-object";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_INSTREAM_FOREIGN_OBJECT;
     }
 
-    /**
-     * Preloads the image so the intrinsic size is available.
-     */
+    /** Preloads the image so the intrinsic size is available. */
     private void prepareIntrinsicSize() {
-        if (intrinsicDimensions == null) {
+        if (!this.instrisicSizeDetermined) {
             XMLObj child = (XMLObj) firstChild;
             Point2D csize = new Point2D.Float(-1, -1);
             intrinsicDimensions = child.getDimension(csize);
             if (intrinsicDimensions == null) {
-                log.error("Intrinsic dimensions of "
-                        + " instream-foreign-object could not be determined");
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.ifoNoIntrinsicSize(this, getLocator());
             }
             intrinsicAlignmentAdjust = child.getIntrinsicAlignmentAdjust();
+            this.instrisicSizeDetermined = true;
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getIntrinsicWidth() {
         prepareIntrinsicSize();
         if (intrinsicDimensions != null) {
@@ -116,9 +118,7 @@ public class InstreamForeignObject extends AbstractGraphics {
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getIntrinsicHeight() {
         prepareIntrinsicSize();
         if (intrinsicDimensions != null) {
@@ -128,11 +128,8 @@ public class InstreamForeignObject extends AbstractGraphics {
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public  Length getIntrinsicAlignmentAdjust()
-    {
+    /** {@inheritDoc} */
+    public  Length getIntrinsicAlignmentAdjust() {
         prepareIntrinsicSize();
         return intrinsicAlignmentAdjust;
     }
index 86c581cc37c3707251a3341c022c071059a922fe..a196e92de3c49efe553b490e63525b430c63b324 100644 (file)
@@ -108,15 +108,17 @@ public class ListBlock extends FObj {
      * XSL Content Model: marker* (list-item)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (hasListItem) {
-                nodesOutOfOrderError(loc, "fo:marker", "fo:list-item");
+            throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (hasListItem) {
+                    nodesOutOfOrderError(loc, "fo:marker", "fo:list-item");
+                }
+            } else if (localName.equals("list-item")) {
+                hasListItem = true;
+            } else {
+                invalidChildError(loc, nsURI, localName);
             }
-        } else if (FO_URI.equals(nsURI) && localName.equals("list-item")) {
-            hasListItem = true;
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
index c09313ef62403b17b463ec641849a4fd0b42d775..cf0e05c565924e2cbadf8be43ace56aef177a35c 100644 (file)
@@ -98,22 +98,24 @@ public class ListItem extends FObj {
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
         throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (label != null) {
-                nodesOutOfOrderError(loc, "fo:marker", "fo:list-item-label");
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (label != null) {
+                    nodesOutOfOrderError(loc, "fo:marker", "fo:list-item-label");
+                }
+            } else if (localName.equals("list-item-label")) {
+                if (label != null) {
+                    tooManyNodesError(loc, "fo:list-item-label");
+                }
+            } else if (localName.equals("list-item-body")) {
+                if (label == null) {
+                    nodesOutOfOrderError(loc, "fo:list-item-label", "fo:list-item-body");
+                } else if (body != null) {
+                    tooManyNodesError(loc, "fo:list-item-body");
+                }
+            } else {
+                invalidChildError(loc, nsURI, localName);
             }
-        } else if (FO_URI.equals(nsURI) && localName.equals("list-item-label")) {
-            if (label != null) {
-                tooManyNodesError(loc, "fo:list-item-label");
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("list-item-body")) {
-            if (label == null) {
-                nodesOutOfOrderError(loc, "fo:list-item-label", "fo:list-item-body");
-            } else if (body != null) {
-                tooManyNodesError(loc, "fo:list-item-body");
-            }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
index 05c9862b2db18db9d718828c802d36f0df0e0865..168b181803b295e70dc863f058415b5c267ec57c 100644 (file)
@@ -59,9 +59,8 @@ public class Marker extends FObjMixed {
      */
     public void bind(PropertyList pList) throws FOPException {
         if (findAncestor(FO_FLOW) < 0) {
-            invalidChildError(locator, FO_URI, "marker", 
-                "An fo:marker is permitted only as the descendant " 
-                    + "of an fo:flow");
+            invalidChildError(locator, getParent().getName(), FO_URI, getName(), 
+                "rule.markerDescendantOfFlow");
         }
         
         markerClassName = pList.get(PR_MARKER_CLASS_NAME).getString();
@@ -112,9 +111,11 @@ public class Marker extends FObjMixed {
      * @todo implement "additional" constraint, possibly within fo:retrieve-marker
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!isBlockOrInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
+            throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!isBlockOrInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
     
index 42ec9d9d0c7375d7f7eff7113489f80b58a9e688..e568fba4661643dd5809248f98a34bdead9b8b2c 100644 (file)
@@ -46,7 +46,8 @@ public class MultiCase extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:multi-case is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
index 00cb85dc79307f2ea631a15a540f8a7dcb935735..bd3bd893ec92d4e265aafdf0c1edcaaba6123c8e 100644 (file)
@@ -49,7 +49,8 @@ public class MultiProperties extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:multi-properties is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -69,13 +70,14 @@ public class MultiProperties extends FObj {
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
         throws ValidationException {
-            if (FO_URI.equals(nsURI) && localName.equals("multi-property-set")) {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("multi-property-set")) {
                 if (hasWrapper) {
                     nodesOutOfOrderError(loc, "fo:multi-property-set", "fo:wrapper");
                 } else {
                     hasMultiPropertySet = true;
                 }
-            } else if (FO_URI.equals(nsURI) && localName.equals("wrapper")) {
+            } else if (localName.equals("wrapper")) {
                 if (hasWrapper) {
                     tooManyNodesError(loc, "fo:wrapper");
                 } else {
@@ -84,6 +86,7 @@ public class MultiProperties extends FObj {
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
+        }
     }
     
     /** {@inheritDoc} */
index 3c9c55b4f4b6aa6b9d473afe137e0fdfb83d23ae..caa31f7b9aeded5647113e5d901908a7ffb34b0c 100644 (file)
@@ -45,7 +45,8 @@ public class MultiPropertySet extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:multi-property-set is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -63,8 +64,10 @@ public class MultiPropertySet extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /** {@inheritDoc} */
index 7c70a7346fef0344f9182050e00c62922000dbbb..03f404aa37b1edbbb5af0f69a2e979b358fcea8d 100644 (file)
@@ -47,7 +47,8 @@ public class MultiSwitch extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:multi-switch is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -75,9 +76,11 @@ public class MultiSwitch extends FObj {
      * XSL Content Model: (multi-case+)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!(FO_URI.equals(nsURI) && localName.equals("multi-case"))) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!localName.equals("multi-case")) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
index 80b36f9f4e743f32bf63a9b08c2f3b327f655b10..66442c2a72aadd7c21c473cf4afbcd0ea0bfe5ea 100644 (file)
@@ -47,7 +47,8 @@ public class MultiToggle extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:multi-toggle is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    getName(), getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -65,9 +66,11 @@ public class MultiToggle extends FObj {
      * XSL Content Model: (#PCDATA|%inline;|%block;)*
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!isBlockOrInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!isBlockOrInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
index 3eca1e16eafe778e6bd3e86ad41f7332daa778af..cc51dd28aeabf7cc3f8f355e8f30e2d91aca08a8 100644 (file)
@@ -117,8 +117,10 @@ public class PageNumber extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /** @return the Common Font Properties. */
index 7356bc72ec9ba0d851586c1d803294d46233f0e9..ea6b6f1c52078a5c56f71eafc7c648379ea4f182 100644 (file)
@@ -21,6 +21,8 @@ package org.apache.fop.fo.flow;
 
 import java.util.Iterator;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FOText;
@@ -30,7 +32,6 @@ import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.flow.table.Table;
 import org.apache.fop.fo.flow.table.TableFObj;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:retrieve-marker object.
@@ -48,21 +49,18 @@ public class RetrieveMarker extends FObjMixed {
 
     /**
      * Create a retrieve marker object.
-     *
+     * @param parent FONode that is the parent of this object
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RetrieveMarker(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         if (findAncestor(FO_STATIC_CONTENT) < 0) {
-            invalidChildError(locator, FO_URI, "retrieve-marker", 
-                "An fo:retrieve-marker is permitted only as the " +
-                " descendant of an fo:static-content.");
+            invalidChildError(locator, getParent().getName(), FO_URI, getName(), 
+                "rule.retrieveMarkerDescendatOfStaticContent");
         }
 
         retrieveClassName = pList.get(PR_RETRIEVE_CLASS_NAME).getString();
@@ -81,8 +79,10 @@ public class RetrieveMarker extends FObjMixed {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
@@ -206,13 +206,12 @@ public class RetrieveMarker extends FObjMixed {
             try {
                 cloneFromMarker(marker);
             } catch (FOPException exc) {
-                log.error("fo:retrieve-marker unable to clone "
-                        + "subtree of fo:marker (marker-class-name="
-                        + marker.getMarkerClassName() + ")", exc);
+                getFOValidationEventProducer().markerCloningFailed(this,
+                        marker.getMarkerClassName(), exc, getLocator());
                 return;
             }
-        } else if (log.isInfoEnabled()) {
-            log.info("Empty marker retrieved...");
+        } else if (log.isDebugEnabled()) {
+            log.debug("Empty marker retrieved...");
         }
         return;
     }
@@ -222,9 +221,7 @@ public class RetrieveMarker extends FObjMixed {
         return "retrieve-marker";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_RETRIEVE_MARKER;
     }    
index adf9b2101d82b718e6ed03b3fd9866a227722892..87582fb476d7db8bd7c40b8838ce2fe101442823 100644 (file)
 
 package org.apache.fop.fo.flow;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObjMixed;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:wrapper object.
@@ -66,17 +67,19 @@ public class Wrapper extends FObjMixed {
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
         throws ValidationException {
-        if (FO_URI.equals(nsURI) && "marker".equals(localName)) {
-            if (blockOrInlineItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", 
-                    "(#PCDATA|%inline;|%block;)");
+        if (FO_URI.equals(nsURI)) {
+            if ("marker".equals(localName)) {
+                if (blockOrInlineItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", 
+                        "(#PCDATA|%inline;|%block;)");
+                }
+            } else if (isBlockOrInlineItem(nsURI, localName)) {
+                //delegate validation to parent
+                FONode.validateChildNode(this.parent, loc, nsURI, localName);
+                blockOrInlineItemFound = true;
+            } else {
+                invalidChildError(loc, nsURI, localName);
             }
-        } else if (isBlockOrInlineItem(nsURI, localName)) {
-            //delegate validation to parent
-            FONode.validateChildNode(this.parent, loc, nsURI, localName);
-            blockOrInlineItemFound = true;
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
index 28a30c6f74a068a5812268e5737180d6cb01f949..0d24491d9b731f64d90c8fadb538ecf73403317d 100644 (file)
@@ -120,13 +120,17 @@ class FixedColRowGroupBuilder extends RowGroupBuilder {
     void endTableRow() {
         assert currentTableRow != null;
         if (currentRowIndex > 0 && currentTableRow.getBreakBefore() != Constants.EN_AUTO) {
-            currentTableRow.attributeWarning("break-before ignored because of row spanning "
-                    + "in progress (See XSL 1.1, 7.20.2)");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    currentTableRow.getUserAgent().getEventBroadcaster());
+            eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), true,
+                    currentTableRow.getLocator());
         }
         if (currentRowIndex < rows.size() - 1
                 && currentTableRow.getBreakAfter() != Constants.EN_AUTO) {
-            currentTableRow.attributeWarning("break-after ignored because of row spanning "
-                    + "in progress (See XSL 1.1, 7.20.1)");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    currentTableRow.getUserAgent().getEventBroadcaster());
+            eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), false,
+                    currentTableRow.getLocator());
         }
         for (Iterator iter = ((List) rows.get(currentRowIndex)).iterator(); iter.hasNext();) {
             GridUnit gu = (GridUnit) iter.next();
index 7d661143517dccc86f7a82123e5094c03fe88584..c1ef3857cdbc36955e84f83711073151ab85bb1c 100644 (file)
@@ -22,6 +22,8 @@ package org.apache.fop.fo.flow.table;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.datatypes.ValidationPercentBaseContext;
@@ -35,7 +37,6 @@ import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.LengthPairProperty;
 import org.apache.fop.fo.properties.LengthRangeProperty;
 import org.apache.fop.fo.properties.TableColLength;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:table object.
@@ -126,20 +127,22 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder {
         orphanContentLimit = pList.get(PR_X_ORPHAN_CONTENT_LIMIT).getLength();
 
         if (!blockProgressionDimension.getOptimum(null).isAuto()) {
-            attributeWarning("only a value of \"auto\" for block-progression-dimension has a"
-                    + " well-specified behavior on fo:table. Falling back to \"auto\"");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.nonAutoBPDOnTable(this, getLocator());
             // Anyway, the bpd of a table is not used by the layout code
         }
         if (tableLayout == EN_AUTO) {
-            attributeWarning("table-layout=\"auto\" is currently not supported by FOP");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    "table-layout=\"auto\"", getLocator());
         }
         if (!isSeparateBorderModel()
                 && getCommonBorderPaddingBackground().hasPadding(
                         ValidationPercentBaseContext.getPseudoContext())) {
             //See "17.6.2 The collapsing border model" in CSS2
-            attributeWarning("In collapsing border model a table does not have padding"
-                    + " (see http://www.w3.org/TR/REC-CSS2/tables.html#collapsing-borders)"
-                    + ", but a non-zero value for padding was found. The padding will be ignored.");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.noTablePaddingWithCollapsingBorderModel(this, getLocator());
         }
 
         /* Store reference to the property list, so
@@ -163,7 +166,7 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder {
      * XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName)
-        throws ValidationException {
+                throws ValidationException {
         if (FO_URI.equals(nsURI)) {
             if ("marker".equals(localName)) {
                 if (tableColumnFound || tableHeaderFound || tableFooterFound
@@ -193,15 +196,11 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder {
                 } else {
                     tableFooterFound = true;
                     if (tableBodyFound) {
-                        if (getUserAgent().validateStrictly()) {
-                            nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)");
-                        } else if (!isSeparateBorderModel()) {
-                            nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)."
-                                    + " This table uses the collapsing border"
-                                    + " model. In order to resolve borders in an efficient way"
-                                    + " the table-footer must be known before any table-body"
-                                    + " is parsed. Either put the footer at the correct place"
-                                    + " or switch to the separate border model");
+                        nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)", true);
+                        if (!isSeparateBorderModel()) {
+                            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                                    getUserAgent().getEventBroadcaster());
+                            eventProducer.footerOrderCannotRecover(this, getName(), getLocator());
                         }
                     }
                 }
@@ -210,8 +209,6 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder {
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
@@ -225,6 +222,10 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder {
                    "(marker*,table-column*,table-header?,table-footer?"
                        + ",table-body+)");
         }
+        if (!hasChildren()) {
+            getParent().removeChild(this);
+            return;
+        }
         if (!inMarker()) {
             rowGroupBuilder.endTable();
             /* clean up */
index f16931cfca41f316cb7cffda4cf18fdb4adfe20f..6dabf37db3d93b6888781cd3bdbb177de53e8476 100644 (file)
@@ -62,7 +62,8 @@ public class TableAndCaption extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:table-and-caption is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    "fo:table-and-caption", getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -83,30 +84,32 @@ public class TableAndCaption extends FObj {
      * XSL Content Model: marker* table-caption? table
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
 
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (tableCaptionFound) {
-                nodesOutOfOrderError(loc, "fo:marker", "fo:table-caption");
-            } else if (tableFound) {
-                nodesOutOfOrderError(loc, "fo:marker", "fo:table");
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("table-caption")) {
-            if (tableCaptionFound) {
-                tooManyNodesError(loc, "fo:table-caption");
-            } else if (tableFound) {
-                nodesOutOfOrderError(loc, "fo:table-caption", "fo:table");
-            } else {
-                tableCaptionFound = true;
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("table")) {
-            if (tableFound) {
-                tooManyNodesError(loc, "fo:table");
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (tableCaptionFound) {
+                    nodesOutOfOrderError(loc, "fo:marker", "fo:table-caption");
+                } else if (tableFound) {
+                    nodesOutOfOrderError(loc, "fo:marker", "fo:table");
+                }
+            } else if (localName.equals("table-caption")) {
+                if (tableCaptionFound) {
+                    tooManyNodesError(loc, "fo:table-caption");
+                } else if (tableFound) {
+                    nodesOutOfOrderError(loc, "fo:table-caption", "fo:table");
+                } else {
+                    tableCaptionFound = true;
+                }
+            } else if (localName.equals("table")) {
+                if (tableFound) {
+                    tooManyNodesError(loc, "fo:table");
+                } else {
+                    tableFound = true;
+                }
             } else {
-                tableFound = true;
+                invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
index de7bfda841169bba35e8c40dc69545b77d0b180e..4e1673568534e635089407deca60d0221120c289 100644 (file)
@@ -23,13 +23,14 @@ import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:table-body object.
@@ -119,13 +120,8 @@ public class TableBody extends TableCellContainer {
         getFOEventHandler().endBody(this);
 
         if (!(tableRowsFound || tableCellsFound)) {
-            if (getUserAgent().validateStrictly()) {
-                missingChildElementError("marker* (table-row+|table-cell+)");
-            } else {
-                log.error("fo:table-body must not be empty. "
-                        + "Expected: marker* (table-row+|table-cell+)");
-                getParent().removeChild(this);
-            }
+            missingChildElementError("marker* (table-row+|table-cell+)", true);
+            getParent().removeChild(this);
         } else {
             finishLastRowGroup();
         }
@@ -167,23 +163,20 @@ public class TableBody extends TableCellContainer {
             } else if (localName.equals("table-row")) {
                 tableRowsFound = true;
                 if (tableCellsFound) {
-                    invalidChildError(loc, nsURI, localName, "Either fo:table-rows"
-                            + " or fo:table-cells may be children of an " + getName()
-                            + " but not both");
+                    TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                    eventProducer.noMixRowsAndCells(this, getName(), getLocator());
                 }
             } else if (localName.equals("table-cell")) {
                 tableCellsFound = true;
                 if (tableRowsFound) {
-                    invalidChildError(loc, nsURI, localName,
-                            "Either fo:table-rows or fo:table-cells "
-                            + "may be children of an "
-                            + getName() + " but not both");
+                    TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                    eventProducer.noMixRowsAndCells(this, getName(), getLocator());
                 }
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
index 28174067c95e888474e7096fa1ddace23e61dbe8..416ef16ed0137f1350144341efc76525baa94753 100644 (file)
@@ -57,7 +57,8 @@ public class TableCaption extends FObj {
         super(parent);
 
         if (!notImplementedWarningGiven) {
-            log.warn("fo:table-caption is not yet implemented.");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    "fo:table-caption", getLocator());
             notImplementedWarningGiven = true;
         }
     }
@@ -83,15 +84,17 @@ public class TableCaption extends FObj {
      * XSL Content Model: marker* (%block;)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+            throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
index 80dbe5e2a13b44ee35cf9273fc8be0eb3e89ec54..78e35eb52af34046d8084d93f7206998bf7fc580 100644 (file)
@@ -102,17 +102,13 @@ public class TableCell extends TableFObj {
      */
     public void endOfNode() throws FOPException {
         if (!blockItemFound) {
-            if (getUserAgent().validateStrictly()) {
-                missingChildElementError("marker* (%block;)+");
-            } else if (firstChild != null) {
-                log.warn("fo:table-cell content that is not "
-                        + "enclosed by a fo:block will be dropped/ignored.");
-            }
+            missingChildElementError("marker* (%block;)+", true);
         }
         if ((startsRow() || endsRow())
                 && getParent().getNameId() == FO_TABLE_ROW ) {
-            log.warn("starts-row/ends-row for fo:table-cells "
-                    + "non-applicable for children of an fo:table-row.");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.startEndRowUnderTableRowWarning(this, getLocator());
         }
         getFOEventHandler().endCell(this);
     }
@@ -123,14 +119,16 @@ public class TableCell extends TableFObj {
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName)
         throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
index 7c91be3513a4ed0f331527e48cd88a07f69f2c34..de9f271b5b2444f921cb159f3f8a582f424940ad 100644 (file)
@@ -24,7 +24,6 @@ import java.util.List;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.ValidationException;
 
 /**
  * A common class for fo:table-body and fo:table-row which both can contain fo:table-cell.
@@ -47,9 +46,9 @@ public abstract class TableCellContainer extends TableFObj implements ColumnNumb
         Table t = getTable();
         if (t.hasExplicitColumns()) {
             if (colNumber + colSpan - 1 > t.getNumberOfColumns()) {
-                throw new ValidationException(FONode.errorText(locator) + "column-number or "
-                        + "number of cells in the row overflows the number of fo:table-column "
-                        + "specified for the table.");
+                TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.tooManyCells(this, getLocator());
             }
         } else {
             t.ensureColumnNumber(colNumber + colSpan - 1);
index aeb401893cf3d73d0556a3d0914eacf5f4b3d428..e6f6b420ef1ccbd3238a87427e6b4697ad03d0bb 100644 (file)
@@ -82,12 +82,16 @@ public class TableColumn extends TableFObj {
         super.bind(pList);
 
         if (numberColumnsRepeated <= 0) {
-            throw new PropertyException("number-columns-repeated must be 1 or bigger, "
-                    + "but got " + numberColumnsRepeated);
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.valueMustBeBiggerGtEqOne(this,
+                    "number-columns-repeated", numberColumnsRepeated, getLocator());
         }
         if (numberColumnsSpanned <= 0) {
-            throw new PropertyException("number-columns-spanned must be 1 or bigger, "
-                    + "but got " + numberColumnsSpanned);
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.valueMustBeBiggerGtEqOne(this,
+                    "number-columns-spanned", numberColumnsSpanned, getLocator());
         }
 
         /* check for unspecified width and replace with default of
@@ -96,8 +100,9 @@ public class TableColumn extends TableFObj {
          */
         if (columnWidth.getEnum() == EN_AUTO) {
             if (!this.implicitColumn && !getTable().isAutoLayout()) {
-                log.warn("table-layout=\"fixed\" and column-width unspecified "
-                        + "=> falling back to proportional-column-width(1)");
+                TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.warnImplicitColumns(this, getLocator());
             }
             columnWidth = new TableColLength(1.0, this);
         }
@@ -146,7 +151,9 @@ public class TableColumn extends TableFObj {
     protected void validateChildNode(Locator loc,
                         String nsURI, String localName)
         throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
@@ -240,7 +247,7 @@ public class TableColumn extends TableFObj {
      * 
      * @param propId    the id for the property to retrieve
      * @return the requested Property
-     * @throws PropertyException
+     * @throws PropertyException if there is a problem evaluating the property 
      */
     public Property getProperty(int propId) throws PropertyException {
         return this.pList.get(propId);
diff --git a/src/java/org/apache/fop/fo/flow/table/TableEventProducer.java b/src/java/org/apache/fop/fo/flow/table/TableEventProducer.java
new file mode 100644 (file)
index 0000000..44ddcc0
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fo.flow.table;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.expr.PropertyException;
+
+/**
+ * Event producer interface for table-specific XSL-FO validation messages.
+ */
+public interface TableEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static TableEventProducer get(EventBroadcaster broadcaster) {
+            return (TableEventProducer)broadcaster.getEventProducerFor(
+                    TableEventProducer.class);
+        }
+    }
+
+    /**
+     * A value other than "auto" has been specified on fo:table.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void nonAutoBPDOnTable(Object source, Locator loc);
+
+    /**
+     * Padding on fo:table is ignored if the collapsing border model is active.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void noTablePaddingWithCollapsingBorderModel(Object source, Locator loc);
+
+    /**
+     * No mixing of table-rows and table-cells is allowed for direct children of table-body.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void noMixRowsAndCells(Object source, String elementName, Locator loc)
+            throws ValidationException;
+
+    /**
+     * The table-footer was found after the table-body. FOP cannot recover with collapsed border
+     * model.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void footerOrderCannotRecover(Object source, String elementName, Locator loc)
+            throws ValidationException;
+    
+    /**
+     * starts-row/ends-row for fo:table-cells non-applicable for children of an fo:table-row
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void startEndRowUnderTableRowWarning(Object source, Locator loc);
+
+    /**
+     * Column-number or number of cells in the row overflows the number of fo:table-column 
+     * specified for the table.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @throws ValidationException the validation error provoked by the method call
+     * @event.severity FATAL
+     */
+    void tooManyCells(Object source, Locator loc) throws ValidationException;
+
+    /**
+     * Property value must be 1 or bigger.
+     * @param source the event source
+     * @param propName the property name
+     * @param actualValue the actual value
+     * @param loc the location of the error or null
+     * @throws PropertyException the property error provoked by the method call
+     * @event.severity FATAL
+     */
+    void valueMustBeBiggerGtEqOne(Object source, String propName,
+            int actualValue, Locator loc) throws PropertyException;
+
+    /**
+     * table-layout=\"fixed\" and column-width unspecified
+     * => falling back to proportional-column-width(1)
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void warnImplicitColumns(Object source, Locator loc);
+
+    /**
+     * padding-* properties are not applicable.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void paddingNotApplicable(Object source, String elementName, Locator loc);
+
+    /**
+     * Cell overlap.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param column the column index of the overlapping cell
+     * @param loc the location of the error or null
+     * @throws PropertyException the property error provoked by the method call
+     * @event.severity FATAL
+     */
+    void cellOverlap(Object source, String elementName, int column,
+            Locator loc) throws PropertyException;
+
+    /**
+     * Break ignored due to row spanning.
+     * @param source the event source
+     * @param elementName the name of the context node
+     * @param breakBefore true for "break-before", false for "break-after"
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void breakIgnoredDueToRowSpanning(Object source, String elementName, boolean breakBefore,
+            Locator loc);
+
+    
+}
index 9618d7ff499af6ea71e2deaa33f8aebb6ae39537..24528f622208398243845aa8b50c9f445cd2a057 100644 (file)
@@ -58,9 +58,7 @@ public abstract class TableFObj extends FObj {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         borderAfterPrecedence = pList.get(PR_BORDER_AFTER_PRECEDENCE).getNumeric();
@@ -71,9 +69,9 @@ public abstract class TableFObj extends FObj {
                 && getNameId() != FO_TABLE_CELL
                 && getCommonBorderPaddingBackground().hasPadding(
                         ValidationPercentBaseContext.getPseudoContext())) {
-            attributeWarning(
-                    "padding-* properties are not applicable to " + getName()
-                    + ", but a non-zero value for padding was found.");
+            TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.paddingNotApplicable(this, getName(), getLocator());
         }
     }
 
@@ -156,17 +154,19 @@ public abstract class TableFObj extends FObj {
             ColumnNumberManager columnIndexManager =  parent.getColumnNumberManager();
             int columnIndex = p.getNumeric().getValue();
             if (columnIndex <= 0) {
+                /* No warning necessary as the spec clearly defines how to handle these cases.
                 log.warn("Specified negative or zero value for "
                         + "column-number on " + fo.getName() + ": "
                         + columnIndex + " forced to "
-                        + columnIndexManager.getCurrentColumnNumber());
+                        + columnIndexManager.getCurrentColumnNumber());*/
                 return NumberProperty.getInstance(columnIndexManager.getCurrentColumnNumber());
             } else {
                 double tmpIndex = p.getNumeric().getNumericValue();
                 if (tmpIndex - columnIndex > 0.0) {
                     columnIndex = (int) Math.round(tmpIndex);
+                    /* No warning necessary as the spec clearly defines how to handle these cases.
                     log.warn("Rounding specified column-number of "
-                            + tmpIndex + " to " + columnIndex);
+                            + tmpIndex + " to " + columnIndex);*/
                     p = NumberProperty.getInstance(columnIndex);
                 }
             }
@@ -179,16 +179,9 @@ public abstract class TableFObj extends FObj {
                     /* if column-number is already in use by another
                      * cell/column => error!
                      */
-                    StringBuffer errorMessage = new StringBuffer();
-                    errorMessage.append(fo.getName() + " overlaps in column ")
-                           .append(columnIndex + i);
-                    org.xml.sax.Locator loc = fo.getLocator();
-                    if (loc != null && loc.getLineNumber() != -1) {
-                        errorMessage.append(" (line #")
-                            .append(loc.getLineNumber()).append(", column #")
-                            .append(loc.getColumnNumber()).append(")");
-                    }
-                    throw new PropertyException(errorMessage.toString());
+                    TableEventProducer eventProducer = TableEventProducer.Provider.get(
+                            fo.getUserAgent().getEventBroadcaster());
+                    eventProducer.cellOverlap(this, fo.getName(), columnIndex + 1, fo.getLocator());
                 }
             }
 
index a025f92fddcb8521adcabd9bcdd8278a0c4061ad..e5261614b2c8da3d6aa9ab6f1bdb29d1b00ae621 100644 (file)
@@ -19,6 +19,9 @@
 
 package org.apache.fop.fo.flow.table;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.fo.FONode;
@@ -27,8 +30,6 @@ import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.KeepProperty;
 import org.apache.fop.fo.properties.LengthRangeProperty;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
 
 /**
  * Class modelling the fo:table-row object.
@@ -122,9 +123,11 @@ public class TableRow extends TableCellContainer {
      */
     protected void validateChildNode(Locator loc, String nsURI,
                                      String localName)
-        throws ValidationException {
-        if (!(FO_URI.equals(nsURI) && localName.equals("table-cell"))) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!localName.equals("table-cell")) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
index 70a09b7202de995f155a920d724556992a79289a..578d74c4ddbaa8a761a9362cb58fff0edec55f46 100644 (file)
@@ -32,7 +32,9 @@ import org.apache.fop.fo.PropertyList;
 public abstract class AbstractPageSequence extends FObj {
     
     // The value of properties relevant for fo:page-sequence.
+    /** the initial-page-number value */
     protected Numeric initialPageNumber;
+    /** the force-page-count value */
     protected int forcePageCount;
     private String format;
     private int letterValue;
@@ -43,6 +45,7 @@ public abstract class AbstractPageSequence extends FObj {
 
     private PageNumberGenerator pageNumberGenerator;
 
+    /** the first page number generated by the page sequence */
     protected int startingPageNumber = 0;
 
     /**
@@ -54,9 +57,7 @@ public abstract class AbstractPageSequence extends FObj {
         super(parent);
     }
 
-    /**
-     * @see org.apache.fop.fo.FObj#bind(PropertyList)
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         initialPageNumber = pList.get(PR_INITIAL_PAGE_NUMBER).getNumeric();
@@ -68,16 +69,14 @@ public abstract class AbstractPageSequence extends FObj {
         referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
     }
 
-    /**
-     * @see org.apache.fop.fo.FONode#startOfNode()
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         this.pageNumberGenerator = new PageNumberGenerator(
                 format, groupingSeparator, groupingSize, letterValue);
 
     }
 
-    /** @see org.apache.fop.fo.FONode#endOfNode() */
+    /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
     }
 
index 9318a8896031c2d405efa9510af5c231ca5e54d9..6067b55c53b2d345657916bb9f5de82b4a8c71bb 100644 (file)
 
 package org.apache.fop.fo.pagination;
 
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 
-import org.xml.sax.Locator;
-
 /**
  * The fo:color-profile formatting object.
  * This loads the color profile when needed and resolves a requested color.
@@ -39,15 +39,15 @@ public class ColorProfile extends FObj {
     // End of property values
 
     /**
+     * Creates a new color-profile element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public ColorProfile(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         src = pList.get(PR_SRC).getString();
         colorProfileName = pList.get(PR_COLOR_PROFILE_NAME).getString();
@@ -59,12 +59,15 @@ public class ColorProfile extends FObj {
         XSL 1.0/FOP: EMPTY (no child nodes permitted)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
      * Return the "color-profile-name" property.
+     * @return the color-profile-name property
      */
     public String getColorProfileName() {
         return colorProfileName;
@@ -75,9 +78,7 @@ public class ColorProfile extends FObj {
         return "color-profile";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_COLOR_PROFILE;
     }
index dfb4ba70b870b2d48a0e4336c151d58627d54505..a13808324ebaaed421ed66611e1dee88c3a81aa6 100644 (file)
@@ -45,15 +45,15 @@ public class ConditionalPageMasterReference extends FObj {
     // End of property values
     
     /**
+     * Creates a new conditional-page-master-reference element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public ConditionalPageMasterReference(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         masterReference = pList.get(PR_MASTER_REFERENCE).getString();
         pagePosition = pList.get(PR_PAGE_POSITION).getEnum();
@@ -65,9 +65,7 @@ public class ConditionalPageMasterReference extends FObj {
         }        
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         getConcreteParent().addConditionalPageMasterReference(this);
     }
@@ -81,8 +79,10 @@ public class ConditionalPageMasterReference extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-           throws ValidationException {
-       invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
index 9dc282d074a6f1a820f727aef962e1259b63ed59..3eec2897a7a8533c13d8ce857f00db654bc461ff 100644 (file)
@@ -42,6 +42,7 @@ public class Declarations extends FObj {
     private Map colorProfiles = null;
 
     /**
+     * Creates a new declarations element.
      * @param parent FONode that is the parent of this object
      */
     public Declarations(FONode parent) {
@@ -49,9 +50,7 @@ public class Declarations extends FObj {
         ((Root) parent).setDeclarations(this);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         // No properties defined for fo:declarations
     }
@@ -73,6 +72,7 @@ public class Declarations extends FObj {
     /**
      * At the end of this element sort out the children into
      * a hashmap of color profiles and a list of extension attachments.
+     * @throws FOPException if there's a problem during processing
      */
     protected void endOfNode() throws FOPException {
         if (firstChild != null) {
@@ -83,7 +83,8 @@ public class Declarations extends FObj {
                     if (!"".equals(cp.getColorProfileName())) {
                         addColorProfile(cp);
                     } else {
-                        log.warn("color-profile-name required for color profile");
+                        getFOValidationEventProducer().missingProperty(this,
+                                cp.getName(), "color-profile-name", locator);
                     }
                 } else {
                     log.debug("Ignoring element " + node.getName() 
@@ -100,22 +101,18 @@ public class Declarations extends FObj {
         }
         if (colorProfiles.get(cp.getColorProfileName()) != null) {
             // duplicate names
-            log.warn("Duplicate fo:color-profile profile name: "
-                    + cp.getColorProfileName());
+            getFOValidationEventProducer().colorProfileNameNotUnique(this,
+                    cp.getName(), cp.getColorProfileName(), locator);
         }
         colorProfiles.put(cp.getColorProfileName(), cp);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public String getLocalName() {
         return "declarations";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_DECLARATIONS;
     }
index fc5d605cc34c89674fe73fe5bfbf7ec327baee27..2ee77ff0a439e57e222a8d425d49b20129c2256d 100644 (file)
@@ -45,16 +45,12 @@ public class Flow extends FObj {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         flowName = pList.get(PR_FLOW_NAME).getString();
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         if (flowName == null || flowName.equals("")) {
             missingPropertyError("flow-name");
@@ -80,10 +76,7 @@ public class Flow extends FObj {
         getFOEventHandler().startFlow(this);
     }
 
-    /**
-     * Make sure content model satisfied, if so then tell the
-     * FOEventHandler that we are at the end of the flow.
-     */
+    /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
         if (!blockItemFound) {
             missingChildElementError("marker* (%block;)+");
@@ -96,21 +89,21 @@ public class Flow extends FObj {
      * XSL Content Model: marker* (%block;)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("marker")) {
-            if (blockItemFound) {
-               nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("marker")) {
+                if (blockItemFound) {
+                   nodesOutOfOrderError(loc, "fo:marker", "(%block;)");
+                }
+            } else if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            } else {
+                blockItemFound = true;
             }
-        } else if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
-        } else {
-            blockItemFound = true;
         }
     }
 
-    /**
-     * @return true (Flow can generate reference areas)
-     */
+    /** {@inheritDoc} */
     public boolean generatesReferenceAreas() {
         return true;
     }
@@ -125,9 +118,7 @@ public class Flow extends FObj {
         return "flow";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_FLOW;
     }
index 54eb297444c4a72063966810b5f5f41a6da0a1c5..1b57be57d27f0bb64a283bc4cf016854f3b0d977 100644 (file)
@@ -46,31 +46,27 @@ public class LayoutMasterSet extends FObj {
     private Map pageSequenceMasters;
 
     /**
+     * Creates a new layout-master-set element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public LayoutMasterSet(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         // No properties in layout-master-set.
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         getRoot().setLayoutMasterSet(this);
         simplePageMasters = new java.util.HashMap();
         pageSequenceMasters = new java.util.HashMap();
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
         if (firstChild == null) {
             missingChildElementError("(simple-page-master|page-sequence-master)+");
@@ -83,14 +79,12 @@ public class LayoutMasterSet extends FObj {
         XSL/FOP: (simple-page-master|page-sequence-master)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+            throws ValidationException {
         if (FO_URI.equals(nsURI)) {
             if (!localName.equals("simple-page-master") 
                 && !localName.equals("page-sequence-master")) {   
                     invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
@@ -104,23 +98,20 @@ public class LayoutMasterSet extends FObj {
         Map allRegions = new java.util.HashMap();
         for (Iterator spm = simplePageMasters.values().iterator();
                 spm.hasNext();) {
-            SimplePageMaster simplePageMaster =
-                (SimplePageMaster)spm.next();
+            SimplePageMaster simplePageMaster
+                (SimplePageMaster)spm.next();
             Map spmRegions = simplePageMaster.getRegions();
             for (Iterator e = spmRegions.values().iterator();
                     e.hasNext();) {
                 Region region = (Region) e.next();
                 if (allRegions.containsKey(region.getRegionName())) {
-                    String defaultRegionName =
-                        (String) allRegions.get(region.getRegionName());
+                    String defaultRegionName
+                        (String) allRegions.get(region.getRegionName());
                     if (!defaultRegionName.equals(region.getDefaultRegionName())) {
-                        throw new ValidationException("Region-name ("
-                                               + region.getRegionName()
-                                               + ") is being mapped to multiple "
-                                               + "region-classes ("
-                                               + defaultRegionName + " and "
-                                               + region.getDefaultRegionName()
-                                               + ")", locator);
+                        getFOValidationEventProducer().regionNameMappedToMultipleRegionClasses(this,
+                                region.getRegionName(),
+                                defaultRegionName,
+                                region.getDefaultRegionName(), getLocator());
                     }
                 }
                 allRegions.put(region.getRegionName(),
@@ -141,21 +132,16 @@ public class LayoutMasterSet extends FObj {
         // check for duplication of master-name
         String masterName = sPM.getMasterName();
         if (existsName(masterName)) {
-            throw new ValidationException("'master-name' ("
-               + masterName
-               + ") must be unique "
-               + "across page-masters and page-sequence-masters", sPM.getLocator());
+            getFOValidationEventProducer().masterNameNotUnique(this,
+                    getName(),
+                    masterName, sPM.getLocator());
         }
         this.simplePageMasters.put(masterName, sPM);
     }
 
     private boolean existsName(String masterName) {
-        if (simplePageMasters.containsKey(masterName)
-                || pageSequenceMasters.containsKey(masterName)) {
-            return true;
-        } else {
-            return false;
-        }
+        return (simplePageMasters.containsKey(masterName)
+                || pageSequenceMasters.containsKey(masterName));
     }
 
     /**
@@ -181,10 +167,9 @@ public class LayoutMasterSet extends FObj {
                 throws ValidationException {
         // check against duplication of master-name
         if (existsName(masterName)) {
-            throw new ValidationException("'master-name' ("
-               + masterName
-               + ") must be unique "
-               + "across page-masters and page-sequence-masters", pSM.getLocator());
+            getFOValidationEventProducer().masterNameNotUnique(this,
+                    getName(),
+                    masterName, pSM.getLocator());
         }
         this.pageSequenceMasters.put(masterName, pSM);
     }
@@ -220,9 +205,7 @@ public class LayoutMasterSet extends FObj {
         return "layout-master-set";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_LAYOUT_MASTER_SET;
     }
index 4289076eadfb0459259fba23736bcb263609d8e8..50620f6781630338e162b5f58a10d32a6c4a3635 100644 (file)
@@ -43,7 +43,7 @@ public class PageNumberGenerator {
     private int minPadding = 0;    // for decimal formats
 
     // preloaded strings of zeros
-    private String zeros[] = {
+    private String[] zeros = {
         "", "0", "00", "000", "0000", "00000"
     };
 
@@ -128,10 +128,10 @@ public class PageNumberGenerator {
     }
 
     private String makeRoman(int num) {
-        int arabic[] = {
+        int[] arabic = {
             1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1
         };
-        String roman[] = {
+        String[] roman = {
             "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv",
             "i"
         };
index 91649fbc5755e17cc3486675cc0379ef819c14d6..3d155a1da4d6c7a5f6e26ea9f652e8e6a903592d 100644 (file)
@@ -78,9 +78,7 @@ public class PageSequence extends AbstractPageSequence {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         country = pList.get(PR_COUNTRY).getString();
@@ -93,9 +91,7 @@ public class PageSequence extends AbstractPageSequence {
         }        
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         super.startOfNode();
         flowMap = new java.util.HashMap();
@@ -105,9 +101,8 @@ public class PageSequence extends AbstractPageSequence {
             this.pageSequenceMaster
                     = getRoot().getLayoutMasterSet().getPageSequenceMaster(masterReference);
             if (this.pageSequenceMaster == null) {
-                throw new ValidationException("master-reference '" + masterReference
-                   + "' for fo:page-sequence matches no"
-                   + " simple-page-master or page-sequence-master", locator);
+                getFOValidationEventProducer().masterNotFound(this, getName(),
+                        masterReference, getLocator());
             }
         }
 
@@ -128,7 +123,7 @@ public class PageSequence extends AbstractPageSequence {
         XSL Content Model: (title?,static-content*,flow)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
         if (FO_URI.equals(nsURI)) {
             if (localName.equals("title")) {
                 if (titleFO != null) {
@@ -149,8 +144,6 @@ public class PageSequence extends AbstractPageSequence {
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
@@ -184,18 +177,15 @@ public class PageSequence extends AbstractPageSequence {
         String flowName = flow.getFlowName();
 
         if (hasFlowName(flowName)) {
-            throw new ValidationException("duplicate flow-name \""
-                + flowName
-                + "\" found within fo:page-sequence", flow.getLocator());
+            getFOValidationEventProducer().duplicateFlowNameInPageSequence(this, flow.getName(),
+                    flowName, flow.getLocator());
         }
 
         if (!getRoot().getLayoutMasterSet().regionNameExists(flowName) 
             && !flowName.equals("xsl-before-float-separator") 
             && !flowName.equals("xsl-footnote-separator")) {
-                throw new ValidationException("flow-name \""
-                    + flowName
-                    + "\" could not be mapped to a region-name in the"
-                    + " layout-master-set", flow.getLocator());
+            getFOValidationEventProducer().flowNameNotMapped(this, flow.getName(),
+                    flowName, flow.getLocator());
         }
     }
 
index 34ad299bdfc5fd52013ff44987c3614afe75eaf0..4258a11398509a56b979de4fb2d04105eaa66241 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.layoutmgr.BlockLevelEventProducer;
 
 /**
  * The page-sequence-master formatting object.
@@ -53,6 +54,8 @@ public class PageSequenceMaster extends FObj {
     // but the actual FO's are MasterReferences.
 
     /**
+     * Creates a new page-sequence-master element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public PageSequenceMaster(FONode parent) {
@@ -95,20 +98,18 @@ public class PageSequenceMaster extends FObj {
      *     repeatable-page-master-alternatives)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
         if (FO_URI.equals(nsURI)) {
             if (!localName.equals("single-page-master-reference") 
                 && !localName.equals("repeatable-page-master-reference")
                 && !localName.equals("repeatable-page-master-alternatives")) {   
                     invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
     /**
-     * Adds a new suqsequence specifier to the page sequence master.
+     * Adds a new subsequence specifier to the page sequence master.
      * @param pageMasterReference the subsequence to add
      */
     protected void addSubsequenceSpecifier(SubSequenceSpecifier pageMasterReference) {
@@ -199,8 +200,10 @@ public class PageSequenceMaster extends FObj {
         if (currentSubSequence == null) {
             currentSubSequence = getNextSubSequence();
             if (currentSubSequence == null) {
-                throw new FOPException("no subsequences in page-sequence-master '"
-                                       + masterName + "'");
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.missingSubsequencesInPageSequenceMaster(this,
+                        masterName, getLocator());
             }
         }
         String pageMasterName = currentSubSequence
@@ -209,14 +212,10 @@ public class PageSequenceMaster extends FObj {
         while (pageMasterName == null) {
             SubSequenceSpecifier nextSubSequence = getNextSubSequence();
             if (nextSubSequence == null) {
-                if (!canRecover) {
-                    throw new FOPException("subsequences exhausted in page-sequence-master '"
-                                           + masterName
-                                           + "', cannot recover");
-                }
-                log.warn("subsequences exhausted in page-sequence-master '"
-                                 + masterName
-                                 + "', using previous subsequence");
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.pageSequenceMasterExhausted(this,
+                        masterName, canRecover, getLocator());
                 currentSubSequence.reset();
                 canRecover = false;
             } else {
@@ -228,9 +227,10 @@ public class PageSequenceMaster extends FObj {
         SimplePageMaster pageMaster = this.layoutMasterSet
             .getSimplePageMaster(pageMasterName);
         if (pageMaster == null) {
-            throw new FOPException("No simple-page-master matching '"
-                                   + pageMasterName + "' in page-sequence-master '"
-                                   + masterName + "'");
+            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.noMatchingPageMaster(this,
+                    masterName, pageMasterName, getLocator());
         }
         return pageMaster;
     }
@@ -240,9 +240,7 @@ public class PageSequenceMaster extends FObj {
         return "page-sequence-master";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_PAGE_SEQUENCE_MASTER;
     }
index 94c0314a3a4a486d15743d31f9d0361672dc478e..0b3cff276054e78ee3ef48829a1859f094c9e051 100644 (file)
@@ -22,8 +22,8 @@ package org.apache.fop.fo.pagination;
 import org.xml.sax.Locator;
 
 import org.apache.fop.apps.FOPException;
-import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 
@@ -39,15 +39,14 @@ public class PageSequenceWrapper extends FObj {
     // End of property values
     
     /**
+     * Creates a new page-sequence-wrapper element.
      * @param parent FONode that is the parent of this object
      */
     public PageSequenceWrapper(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         indexClass = pList.get(PR_INDEX_CLASS).getString();
@@ -59,10 +58,12 @@ public class PageSequenceWrapper extends FObj {
         XSL/FOP: (bookmark+)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!(FO_URI.equals(nsURI) && (localName.equals("page-sequence") || 
-            localName.equals("page-sequence-wrapper")))) {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!(localName.equals("page-sequence")
+                    || localName.equals("page-sequence-wrapper"))) {
                 invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
@@ -81,9 +82,7 @@ public class PageSequenceWrapper extends FObj {
         return "page-sequence-wrapper";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_PAGE_SEQUENCE_WRAPPER;
     }
index 2516f90d8b7140f17bc87f8627eb28ce48f6448b..ded86514bb213967bd43ba97249fd5edb8dcaa6c 100644 (file)
@@ -30,7 +30,6 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
-import org.apache.fop.fo.expr.PropertyException;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 
 /**
@@ -50,6 +49,8 @@ public abstract class Region extends FObj {
     private SimplePageMaster layoutMaster;
 
     /**
+     * Creates a new Region.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     protected Region(FONode parent) {
@@ -57,9 +58,7 @@ public abstract class Region extends FObj {
         layoutMaster = (SimplePageMaster) parent;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps();
         // clip = pList.get(PR_CLIP);
@@ -76,18 +75,16 @@ public abstract class Region extends FObj {
             // check that name is OK. Not very pretty.
             if (isReserved(getRegionName())
                     && !getRegionName().equals(getDefaultRegionName())) {
-                throw new ValidationException("region-name '" + regionName
-                        + "' for " + this.getName()
-                        + " is not permitted.", locator);
+                getFOValidationEventProducer().illegalRegionName(this, getName(),
+                        regionName, getLocator());
             }
         }
         
         //TODO do we need context for getBPPaddingAndBorder() and getIPPaddingAndBorder()?
-        if (getUserAgent().validateStrictly()
-                && (getCommonBorderPaddingBackground().getBPPaddingAndBorder(false, null) != 0 
+        if ((getCommonBorderPaddingBackground().getBPPaddingAndBorder(false, null) != 0 
                 || getCommonBorderPaddingBackground().getIPPaddingAndBorder(false, null) != 0)) {
-            throw new PropertyException("Border and padding for region \""
-                    + regionName + "\" must be '0' (See 6.4.13 in XSL 1.0).");
+            getFOValidationEventProducer().nonZeroBorderPaddingOnRegion(this, getName(),
+                    regionName, true, getLocator());
         }
     }
 
@@ -96,8 +93,10 @@ public abstract class Region extends FObj {
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
@@ -121,7 +120,7 @@ public abstract class Region extends FObj {
      * @param name a region name to check
      * @return true if the name parameter is a reserved region name
      */
-    protected boolean isReserved(String name) /*throws FOPException*/ {
+    protected boolean isReserved(String name) {
         return (name.equals("xsl-region-before")
                 || name.equals("xsl-region-start")
                 || name.equals("xsl-region-end")
@@ -130,9 +129,7 @@ public abstract class Region extends FObj {
                 || name.equals("xsl-footnote-separator"));
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public boolean generatesReferenceAreas() {
         return true;
     }
index 9459a66372703e3bd690b1a3235df070c3ef95a5..2852358b5e86bbec749f8efd49cf5e2b245e400c 100644 (file)
@@ -34,15 +34,15 @@ import org.apache.fop.datatypes.SimplePercentBaseContext;
 public class RegionAfter extends RegionBA {
 
     /**
+     * Creates a new region-after element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RegionAfter(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) {
         /* Special rules apply to resolving extent as values are resolved relative 
          * to the page size and reference orientation.
@@ -82,9 +82,7 @@ public class RegionAfter extends RegionBA {
         return vpRect;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected String getDefaultRegionName() {
         return "xsl-region-after";
     }
@@ -94,9 +92,7 @@ public class RegionAfter extends RegionBA {
         return "region-after";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_REGION_AFTER;
     }
index 768af2489b5d8d2269a30e13ed834933b43fb61d..279164a9688e369d7b10bea31a3d4ab727f94445 100644 (file)
@@ -36,15 +36,15 @@ public abstract class RegionBA extends SideRegion {
     // End of property values
     
     /**
+     * Creates a new region (before or after).
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     protected RegionBA(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         precedence = pList.get(PR_PRECEDENCE).getEnum();
index 6115d8dd8f57b1032d8ed9e705cdbffef3e12531..076737252707ebea9459231498a75e1180be704a 100644 (file)
@@ -32,23 +32,22 @@ import org.apache.fop.fo.FONode;
  * The fo:region-before element.
  */
 public class RegionBefore extends RegionBA {
+
     /**
+     * Creates a new region-before element.
+     * @param parent the parent element
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RegionBefore(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected String getDefaultRegionName() {
         return "xsl-region-before";
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) {
         /* Special rules apply to resolving extent as values are resolved relative 
          * to the page size and reference orientation.
@@ -91,9 +90,7 @@ public class RegionBefore extends RegionBA {
         return "region-before";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_REGION_BEFORE;
     }
index 4de2dd1b4d1513b6dfec1586c79fc59b8f627902..9700e72fc057565fbff8b4eb675d0c7e6d37d9f1 100644 (file)
@@ -43,29 +43,29 @@ public class RegionBody extends Region {
     // End of property values
 
     /**
+     * Creates a new region-body element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RegionBody(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         commonMarginBlock = pList.getMarginBlockProps();
         columnCount = pList.get(PR_COLUMN_COUNT).getNumeric();
         columnGap = pList.get(PR_COLUMN_GAP).getLength();
         
-        if ((getColumnCount() > 1) && (getOverflow() == EN_SCROLL)) {
+        if ((getColumnCount() != 1) && (getOverflow() == EN_SCROLL)) {
             /* This is an error (See XSL Rec, fo:region-body description).
              * The Rec allows for acting as if "1" is chosen in
              * these cases, but we will need to be able to change Numeric
              * values in order to do this.
              */
-            attributeError("If overflow property is set to \"scroll\"," 
-                    + " a column-count other than \"1\" may not be specified.");
+            getFOValidationEventProducer().columnCountErrorOnRegionBodyOverflowScroll(this,
+                    getName(), getLocator());
         }
     }
 
@@ -93,9 +93,7 @@ public class RegionBody extends Region {
         return columnGap.getValue();
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) {
         /* Special rules apply to resolving margins in the page context.
          * Contrary to normal margins in this case top and bottom margin
@@ -143,9 +141,7 @@ public class RegionBody extends Region {
                     reldims.bpd - before - after);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected String getDefaultRegionName() {
         return "xsl-region-body";
     }
@@ -155,9 +151,7 @@ public class RegionBody extends Region {
         return "region-body";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_REGION_BODY;
     }
index 912be909722b790c74dfd35dbb8e87ba91d0d4e9..13f65d71ae9700cfd26487f6cbe56895e172922b 100644 (file)
@@ -22,26 +22,26 @@ package org.apache.fop.fo.pagination;
 // Java
 import java.awt.Rectangle;
 
-// FOP
-import org.apache.fop.fo.FONode;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.LengthBase;
 import org.apache.fop.datatypes.SimplePercentBaseContext;
+import org.apache.fop.fo.FONode;
 
 /**
  * The fo:region-end element.
  */
 public class RegionEnd extends RegionSE {
+
     /**
+     * Creates a new region-end element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RegionEnd(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) {
         /* Special rules apply to resolving extent as values are resolved relative 
          * to the page size and reference orientation.
@@ -80,9 +80,7 @@ public class RegionEnd extends RegionSE {
         return vpRect;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected String getDefaultRegionName() {
         return "xsl-region-end";
     }
@@ -92,9 +90,7 @@ public class RegionEnd extends RegionSE {
         return "region-end";
     }
     
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_REGION_END;
     }
index 7356233523a98a23487fc8751949fbe5d70e7eac..183b44342b490548b2224d88e6dd88c53df7e0ac 100644 (file)
@@ -35,15 +35,15 @@ public abstract class RegionSE extends SideRegion {
     // End of property values
 
     /**
+     * Creates a new region (start or end).
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     protected RegionSE(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
     }
index d78b19c3d569712df810c0eed553c2d1d87871f0..7a69cfdacffb3dfe0402f42177b72fe8b7083909 100644 (file)
@@ -32,16 +32,17 @@ import org.apache.fop.datatypes.SimplePercentBaseContext;
  * The fo:region-start element.
  */
 public class RegionStart extends RegionSE {
+
     /**
+     * Creates a new region-start element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RegionStart(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) {
         /* Special rules apply to resolving extent as values are resolved relative 
          * to the page size and reference orientation.
@@ -77,9 +78,7 @@ public class RegionStart extends RegionSE {
         return vpRect;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected String getDefaultRegionName() {
         return "xsl-region-start";
     }
@@ -89,9 +88,7 @@ public class RegionStart extends RegionSE {
         return "region-start";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_REGION_START;
     }
index 9d2fe652ca8316d667fca7ee13d2a7d521deeed8..509b81f21ad17cf3aa7fcbe7e3a6d4905cf4b2ba 100644 (file)
@@ -52,38 +52,29 @@ public class RepeatablePageMasterAlternatives extends FObj
     private boolean hasPagePositionOnly = false;
 
     /**
+     * Creates a new repeatable-page-master-alternatives element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RepeatablePageMasterAlternatives(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         maximumRepeats = pList.get(PR_MAXIMUM_REPEATS);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         conditionalPageMasterRefs = new java.util.ArrayList();
 
-        if (parent.getName().equals("fo:page-sequence-master")) {
-            PageSequenceMaster pageSequenceMaster = (PageSequenceMaster)parent;
-            pageSequenceMaster.addSubsequenceSpecifier(this);
-        } else {
-            throw new ValidationException("fo:repeatable-page-master-alternatives "
-                                   + "must be child of fo:page-sequence-master, not "
-                                   + parent.getName(), locator);
-        }
+        assert parent.getName().equals("fo:page-sequence-master"); //Validation by the parent 
+        PageSequenceMaster pageSequenceMaster = (PageSequenceMaster)parent;
+        pageSequenceMaster.addSubsequenceSpecifier(this);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
         if (firstChild == null) {
            missingChildElementError("(conditional-page-master-reference+)");
@@ -95,10 +86,11 @@ public class RepeatablePageMasterAlternatives extends FObj
         XSL/FOP: (conditional-page-master-reference+)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!(FO_URI.equals(nsURI)
-                && localName.equals("conditional-page-master-reference"))) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!localName.equals("conditional-page-master-reference")) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
@@ -117,11 +109,7 @@ public class RepeatablePageMasterAlternatives extends FObj
         }
     }
 
-    /**
-     * Get the next matching page master from the conditional
-     * page master references.
-     * @see org.apache.fop.fo.pagination.SubSequenceSpecifier
-     */
+    /** {@inheritDoc} */
     public String getNextPageMasterName(boolean isOddPage,
                                         boolean isFirstPage,
                                         boolean isLastPage,
@@ -183,12 +171,11 @@ public class RepeatablePageMasterAlternatives extends FObj
     }
     
     /** {@inheritDoc} */
-    /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#hasPagePositionOnly() */
     public boolean hasPagePositionOnly() {
         return this.hasPagePositionOnly;
     }
     
-    /** @see org.apache.fop.fo.FONode#getLocalName() */
+    /** {@inheritDoc} */
     public String getLocalName() {
         return "repeatable-page-master-alternatives";
     }
index 172324232b6707df678cb0678847239a498b3bab..87dc248c06111edc85937a2a3f4c7c1e23b84352 100644 (file)
@@ -47,15 +47,15 @@ public class RepeatablePageMasterReference extends FObj
     private int numberConsumed = 0;
 
     /**
+     * Creates a new repeatable-page-master-reference element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public RepeatablePageMasterReference(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         masterReference = pList.get(PR_MASTER_REFERENCE).getString();
         maximumRepeats = pList.get(PR_MAXIMUM_REPEATS);
@@ -65,9 +65,7 @@ public class RepeatablePageMasterReference extends FObj
         }        
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         PageSequenceMaster pageSequenceMaster = (PageSequenceMaster) parent;
 
@@ -83,13 +81,13 @@ public class RepeatablePageMasterReference extends FObj
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            invalidChildError(loc, nsURI, localName);
+        }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public String getNextPageMasterName(boolean isOddPage,
                                         boolean isFirstPage,
                                         boolean isLastPage,
index 6e079cf47d90f55e7db590ed476aaaba4c75d416..1cff9c3d600b0870bc5fb5d4aa1a67a0fa655064 100644 (file)
@@ -25,7 +25,6 @@ import java.util.List;
 import org.xml.sax.Locator;
 
 import org.apache.fop.apps.FOPException;
-import org.apache.fop.fo.ElementMapping;
 import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
@@ -62,7 +61,9 @@ public class Root extends FObj {
      */
     private FOEventHandler foEventHandler = null;
      
-     /**
+    /**
+     * Creates a new root element.
+     * @param parent the parent node (must be null)
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public Root(FONode parent) {
@@ -137,13 +138,6 @@ public class Root extends FObj {
     }
     
 
-    /** @inheritDoc */
-    protected void validateChildNode(Locator loc, FONode child) throws ValidationException {
-        if (child instanceof AbstractPageSequence) {
-            pageSequenceFound = true;
-        }
-    }
-
     /**
      * Sets the FOEventHandler object that this Root is attached to
      * @param foEventHandler the FOEventHandler object
@@ -291,9 +285,7 @@ public class Root extends FObj {
         return bookmarkTree;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public Root getRoot() {
         return this;
     }
@@ -303,9 +295,7 @@ public class Root extends FObj {
         return "root";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_ROOT;
     }
index 14328aa9b1473efae72ca08cac94dce1e81a40ef..552ca871b18cba977a1a843762f827d4bb2e3476 100644 (file)
@@ -31,7 +31,11 @@ public abstract class SideRegion extends Region {
 
     private Length extent;
     
-    /** @see org.apache.fop.fo.FONode#FONode(FONode) */
+    /**
+     * Creates a new side region.
+     * @param parent the parent node
+     * @see org.apache.fop.fo.FONode#FONode(FONode)
+     */
     protected SideRegion(FONode parent) {
         super(parent);
     }
index ba1c0a6af4a3ec2810b2a0d96979c5853170ae0e..85a5081c8620789ccf0690c7e1253dea5639f22c 100644 (file)
@@ -63,15 +63,15 @@ public class SimplePageMaster extends FObj {
     private boolean hasRegionEnd = false;
 
     /**
+     * Creates a new simple-page-master element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public SimplePageMaster(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         commonMarginBlock = pList.getMarginBlockProps();
         masterName = pList.get(PR_MASTER_NAME).getString();
@@ -85,9 +85,7 @@ public class SimplePageMaster extends FObj {
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         LayoutMasterSet layoutMasterSet = (LayoutMasterSet) parent;
 
@@ -101,9 +99,7 @@ public class SimplePageMaster extends FObj {
         regions = new HashMap(5);
     }
 
-    /**
-     * Make sure content model satisfied.
-     */
+    /** {@inheritDoc} */
     protected void endOfNode() throws FOPException {
         if (!hasRegionBody) {
             missingChildElementError(
@@ -116,72 +112,70 @@ public class SimplePageMaster extends FObj {
      * XSL Content Model: (region-body,region-before?,region-after?,region-start?,region-end?)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (FO_URI.equals(nsURI) && localName.equals("region-body")) {
-            if (hasRegionBody) {
-                tooManyNodesError(loc, "fo:region-body");
-            } else {
-                hasRegionBody = true;
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("region-before")) {
-            if (!hasRegionBody) {
-                nodesOutOfOrderError(loc, "fo:region-body", "fo:region-before");
-            } else if (hasRegionBefore) {
-                tooManyNodesError(loc, "fo:region-before");
-            } else if (hasRegionAfter) {
-                nodesOutOfOrderError(loc, "fo:region-before", "fo:region-after");
-            } else if (hasRegionStart) {
-                nodesOutOfOrderError(loc, "fo:region-before", "fo:region-start");
-            } else if (hasRegionEnd) {
-                nodesOutOfOrderError(loc, "fo:region-before", "fo:region-end");
-            } else {
-                hasRegionBody = true;
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("region-after")) {
-            if (!hasRegionBody) {
-                nodesOutOfOrderError(loc, "fo:region-body", "fo:region-after");
-            } else if (hasRegionAfter) {
-                tooManyNodesError(loc, "fo:region-after");
-            } else if (hasRegionStart) {
-                nodesOutOfOrderError(loc, "fo:region-after", "fo:region-start");
-            } else if (hasRegionEnd) {
-                nodesOutOfOrderError(loc, "fo:region-after", "fo:region-end");
-            } else {
-                hasRegionAfter = true;
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("region-start")) {
-            if (!hasRegionBody) {
-                nodesOutOfOrderError(loc, "fo:region-body", "fo:region-start");
-            } else if (hasRegionStart) {
-                tooManyNodesError(loc, "fo:region-start");
-            } else if (hasRegionEnd) {
-                nodesOutOfOrderError(loc, "fo:region-start", "fo:region-end");
-            } else {
-                hasRegionStart = true;
-            }
-        } else if (FO_URI.equals(nsURI) && localName.equals("region-end")) {
-            if (!hasRegionBody) {
-                nodesOutOfOrderError(loc, "fo:region-body", "fo:region-end");
-            } else if (hasRegionEnd) {
-                tooManyNodesError(loc, "fo:region-end");
+            throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("region-body")) {
+                if (hasRegionBody) {
+                    tooManyNodesError(loc, "fo:region-body");
+                } else {
+                    hasRegionBody = true;
+                }
+            } else if (localName.equals("region-before")) {
+                if (!hasRegionBody) {
+                    nodesOutOfOrderError(loc, "fo:region-body", "fo:region-before");
+                } else if (hasRegionBefore) {
+                    tooManyNodesError(loc, "fo:region-before");
+                } else if (hasRegionAfter) {
+                    nodesOutOfOrderError(loc, "fo:region-before", "fo:region-after");
+                } else if (hasRegionStart) {
+                    nodesOutOfOrderError(loc, "fo:region-before", "fo:region-start");
+                } else if (hasRegionEnd) {
+                    nodesOutOfOrderError(loc, "fo:region-before", "fo:region-end");
+                } else {
+                    hasRegionBody = true;
+                }
+            } else if (localName.equals("region-after")) {
+                if (!hasRegionBody) {
+                    nodesOutOfOrderError(loc, "fo:region-body", "fo:region-after");
+                } else if (hasRegionAfter) {
+                    tooManyNodesError(loc, "fo:region-after");
+                } else if (hasRegionStart) {
+                    nodesOutOfOrderError(loc, "fo:region-after", "fo:region-start");
+                } else if (hasRegionEnd) {
+                    nodesOutOfOrderError(loc, "fo:region-after", "fo:region-end");
+                } else {
+                    hasRegionAfter = true;
+                }
+            } else if (localName.equals("region-start")) {
+                if (!hasRegionBody) {
+                    nodesOutOfOrderError(loc, "fo:region-body", "fo:region-start");
+                } else if (hasRegionStart) {
+                    tooManyNodesError(loc, "fo:region-start");
+                } else if (hasRegionEnd) {
+                    nodesOutOfOrderError(loc, "fo:region-start", "fo:region-end");
+                } else {
+                    hasRegionStart = true;
+                }
+            } else if (localName.equals("region-end")) {
+                if (!hasRegionBody) {
+                    nodesOutOfOrderError(loc, "fo:region-body", "fo:region-end");
+                } else if (hasRegionEnd) {
+                    tooManyNodesError(loc, "fo:region-end");
+                } else {
+                    hasRegionEnd = true;
+                }
             } else {
-                hasRegionEnd = true;
+                invalidChildError(loc, nsURI, localName);
             }
-        } else {
-            invalidChildError(loc, nsURI, localName);
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public boolean generatesReferenceAreas() {
         return true;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void addChildNode(FONode child) throws FOPException {
         if (child instanceof Region) {
             addRegion((Region)child);
@@ -268,9 +262,7 @@ public class SimplePageMaster extends FObj {
         return "simple-page-master";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_SIMPLE_PAGE_MASTER;
     }
index 43d8e40dc348fa4b1488c22a2463ecebbb3c6738..119ec409e97a0ae694d3ee2837f66ac2107bab8e 100644 (file)
@@ -46,6 +46,8 @@ public class SinglePageMasterReference extends FObj
     private int state;
 
     /**
+     * Creates a new single-page-master-reference element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public SinglePageMasterReference(FONode parent) {
@@ -53,9 +55,7 @@ public class SinglePageMasterReference extends FObj
         this.state = FIRST;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void bind(PropertyList pList) throws FOPException {
         masterReference = pList.get(PR_MASTER_REFERENCE).getString();
 
@@ -64,9 +64,7 @@ public class SinglePageMasterReference extends FObj
         }        
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         PageSequenceMaster pageSequenceMaster = (PageSequenceMaster) parent;
         pageSequenceMaster.addSubsequenceSpecifier(this);
@@ -77,8 +75,10 @@ public class SinglePageMasterReference extends FObj
      * XSL Content Model: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-       invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /** {@inheritDoc} */
index 62d73e56d57462ac08f85a5e98f3714b1eebf97f..184438b6f068516f48e950e1220035a04f20c853 100644 (file)
@@ -27,24 +27,22 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.ValidationException;
 
 /**
- * Class modelling the fo:static-content object.
+ * Class modeling the fo:static-content object.
  */
 public class StaticContent extends Flow {
 
     /**
+     * Creates a new static-content element.
      * @param parent FONode that is the parent of this object
      */
     public StaticContent(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void startOfNode() throws FOPException {
         if (getFlowName() == null || getFlowName().equals("")) {
-            throw new ValidationException("A 'flow-name' is required for "
-                                   + getName() + ".", locator);
+            missingPropertyError("flow-name");
         }
         getFOEventHandler().startFlow(this);
     }
@@ -66,9 +64,11 @@ public class StaticContent extends Flow {
      * XSL Content Model: (%block;)+
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!isBlockItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!isBlockItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
index 398424152059fa349ae82504f5f92a1420ae9089..f6f625ea806720f18c02e35adc70c2bd67f125bb 100644 (file)
@@ -27,7 +27,7 @@ import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.flow.InlineLevel;
 
 /**
- * Class modelling the fo:title object.
+ * Class modeling the fo:title object.
  */
 public class Title extends InlineLevel {
     // The value of properties relevant for fo:title.
@@ -35,6 +35,7 @@ public class Title extends InlineLevel {
     // End of property values
 
     /**
+     * Creates a new title element.
      * @param parent FONode that is the parent of this object
      */
     public Title(FONode parent) {
@@ -46,22 +47,20 @@ public class Title extends InlineLevel {
         XSL/FOP: (#PCDATA|%inline;)*
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!isInlineItem(nsURI, localName)) {
-            invalidChildError(loc, nsURI, localName);
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!isInlineItem(nsURI, localName)) {
+                invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public String getLocalName() {
         return "title";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_TITLE;
     }
index e588bb3f8419a4fc10c3896890aea2c38518790a..7f55ec51b35f7682b22bb9c4173f72a4c5115bb0 100644 (file)
 package org.apache.fop.fo.pagination.bookmarks;
 
 import java.util.ArrayList;
+import java.util.List;
+
 import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
-import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 
@@ -68,10 +71,10 @@ public class Bookmark extends FObj {
             externalDestination = null;
         } else if (externalDestination.length() == 0) {
             // slightly stronger than spec "should be specified"
-            attributeError("Missing attribute:  Either external-destination or " +
-                "internal-destination must be specified.");
+            getFOValidationEventProducer().missingLinkDestination(this, getName(), locator);
         } else {
-            attributeWarning("external-destination property not currently supported");
+            getFOValidationEventProducer().unimplementedFeature(this, getName(),
+                    "external-destination", getLocator());
         }
     }
 
@@ -80,18 +83,20 @@ public class Bookmark extends FObj {
         XSL/FOP: (bookmark-title, bookmark*)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-            if (FO_URI.equals(nsURI) && localName.equals("bookmark-title")) {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (localName.equals("bookmark-title")) {
                 if (bookmarkTitle != null) {
                     tooManyNodesError(loc, "fo:bookmark-title");
                 }
-            } else if (FO_URI.equals(nsURI) && localName.equals("bookmark")) {
+            } else if (localName.equals("bookmark")) {
                 if (bookmarkTitle == null) {
                     nodesOutOfOrderError(loc, "fo:bookmark-title", "fo:bookmark");
                 }                
             } else {
                 invalidChildError(loc, nsURI, localName);
             }
+        }
     }
 
     /**
@@ -123,10 +128,18 @@ public class Bookmark extends FObj {
         return bookmarkTitle == null ? "" : bookmarkTitle.getTitle();
     }
 
+    /**
+     * Returns the value of the internal-destination property.
+     * @return the internal-destination
+     */
     public String getInternalDestination() {
         return internalDestination;
     }
 
+    /**
+     * Returns the value of the external-destination property.
+     * @return the external-destination
+     */
     public String getExternalDestination() {
         return externalDestination;
     }
@@ -141,7 +154,11 @@ public class Bookmark extends FObj {
         return bShow;
     }
 
-    public ArrayList getChildBookmarks() {
+    /**
+     * Returns a list of child bookmarks.
+     * @return the list of child bookmarks
+     */
+    public List getChildBookmarks() {
         return childBookmarks;
     }
 
@@ -150,9 +167,7 @@ public class Bookmark extends FObj {
         return "bookmark";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_BOOKMARK;
     }
index f31aad1cbb82cbfad8cf6cc61f312cce666113c3..c7024f2aae731d02c1e8e64a9abbad28b4edfc4d 100644 (file)
@@ -21,8 +21,8 @@ package org.apache.fop.fo.pagination.bookmarks;
 
 import org.xml.sax.Locator;
 
-import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 
@@ -45,15 +45,16 @@ public class BookmarkTitle extends FObj {
 
     /**
      * Add the characters to this BookmarkTitle.
-     * The text data inside the BookmarkTitle xml element 
+     * The text data inside the BookmarkTitle XML element 
      * is used for the BookmarkTitle string.
      *
      * @param data the character data
      * @param start the start position in the data array
      * @param end the end position in the character array
-     * @param locator location in fo source file.
+     * @param pList the currently valid property list
+     * @param locator location in FO source file.
      */
-    protected void addCharacters(char data[], int start, int end,
+    protected void addCharacters(char[] data, int start, int end,
                                  PropertyList pList,
                                  Locator locator) {
         title += new String(data, start, end - start);
@@ -64,8 +65,10 @@ public class BookmarkTitle extends FObj {
         XSL/FOP: empty
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
             invalidChildError(loc, nsURI, localName);
+        }
     }
 
     /**
@@ -82,9 +85,7 @@ public class BookmarkTitle extends FObj {
         return "bookmark-title";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_BOOKMARK_TITLE;
     }
index 6190fa22ae046e3c91a6d9c10e24962bd4b0c1f6..0f1d8a8b7bf19ed1f0d1d368da011eed246a5d9c 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.fo.pagination.bookmarks;
 
 // Java
 import java.util.ArrayList;
+import java.util.List;
 
 import org.xml.sax.Locator;
 
@@ -39,15 +40,15 @@ public class BookmarkTree extends FObj {
     private ArrayList bookmarks = new ArrayList();
 
     /**
+     * Creates a new bookmark-tree element.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public BookmarkTree(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void addChildNode(FONode obj) {
         if (obj instanceof Bookmark) {
             bookmarks.add(obj);
@@ -69,14 +70,19 @@ public class BookmarkTree extends FObj {
         XSL/FOP: (bookmark+)
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
-        throws ValidationException {
-        if (!(FO_URI.equals(nsURI) &&
-            localName.equals("bookmark"))) {
+                throws ValidationException {
+        if (FO_URI.equals(nsURI)) {
+            if (!localName.equals("bookmark")) {
                 invalidChildError(loc, nsURI, localName);
+            }
         }
     }
 
-    public ArrayList getBookmarks() {
+    /**
+     * Returns the root bookmarks.
+     * @return the root bookmarks
+     */
+    public List getBookmarks() {
         return bookmarks;
     }
 
@@ -85,9 +91,7 @@ public class BookmarkTree extends FObj {
         return "bookmark-tree";
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public int getNameId() {
         return FO_BOOKMARK_TREE;
     }
index 69ca372e5c15814d7d52fb1340642d040fe0d0d3..5a5cf95c5aaba3ba4f200827eca3b62266662628 100644 (file)
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
-/* $Id:$ */
+/* $Id$ */
 
 package org.apache.fop.fo.properties;
 
diff --git a/src/java/org/apache/fop/fonts/FontEventAdapter.java b/src/java/org/apache/fop/fonts/FontEventAdapter.java
new file mode 100644 (file)
index 0000000..516999f
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.fonts;
+
+import java.util.Map;
+
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.model.EventSeverity;
+
+/**
+ * Event listener interface for font-related events. This interface extends FontEventListener
+ * and EventProducer for integration into FOP's event subsystem.
+ */
+public class FontEventAdapter implements FontEventListener {
+
+    private EventBroadcaster eventBroadcaster;
+    
+    /**
+     * Creates a new FontEventAdapter.
+     * @param broadcaster the event broadcaster to send the generated events to
+     */
+    public FontEventAdapter(EventBroadcaster broadcaster) {
+        this.eventBroadcaster = broadcaster;
+    }
+    
+    /**
+     * Returns the event group ID.
+     * @return the event group ID
+     */
+    protected String getEventGroupID() {
+        return getClass().getName();
+    }
+    
+    /** {@inheritDoc} */
+    public void fontSubstituted(Object source, FontTriplet requested, FontTriplet effective) {
+        Map params = new java.util.HashMap();
+        params.put("requested", requested);
+        params.put("effective", effective);
+        Event ev = new Event(source, getEventGroupID() + ".fontSubstituted",
+                EventSeverity.WARN, params);
+        this.eventBroadcaster.broadcastEvent(ev);
+    }
+
+    /** {@inheritDoc} */
+    public void fontLoadingErrorAtAutoDetection(Object source, String fontURL, Exception e) {
+        Map params = new java.util.HashMap();
+        params.put("fontURL", fontURL);
+        params.put("e", e);
+        Event ev = new Event(source, getEventGroupID() + ".fontLoadingErrorAtAutoDetection",
+                EventSeverity.WARN, params);
+        this.eventBroadcaster.broadcastEvent(ev);
+    }
+
+    /** {@inheritDoc} */
+    public void glyphNotAvailable(Object source, char ch, String fontName) {
+        Map params = new java.util.HashMap();
+        params.put("ch", new Character(ch));
+        params.put("fontName", fontName);
+        Event ev = new Event(source, getEventGroupID() + ".glyphNotAvailable",
+                EventSeverity.WARN, params);
+        this.eventBroadcaster.broadcastEvent(ev);
+    }
+    
+}
diff --git a/src/java/org/apache/fop/fonts/FontEventListener.java b/src/java/org/apache/fop/fonts/FontEventListener.java
new file mode 100644 (file)
index 0000000..512df0a
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.fonts;
+
+
+/**
+ * Event listener interface for font-related events.
+ */
+public interface FontEventListener {
+
+    /**
+     * Notifies about a font being substituted as the requested one isn't available.
+     * @param source the event source
+     * @param requested the requested font triplet
+     * @param effective the effective font triplet
+     */
+    void fontSubstituted(Object source, FontTriplet requested, FontTriplet effective);
+
+    /**
+     * An error occurred while loading a font for auto-detection.
+     * @param source the event source
+     * @param fontURL the font URL
+     * @param e the original exception
+     */
+    void fontLoadingErrorAtAutoDetection(Object source, String fontURL, Exception e);
+
+    /**
+     * A glyph has been requested that is not available in the font.
+     * @param source the event source
+     * @param ch the character for which the glyph isn't available
+     * @param fontName the name of the font
+     */
+    void glyphNotAvailable(Object source, char ch, String fontName);
+    
+}
index 218734f1575bcac705bfaaa6ad6f5513ddb249df..950134eb6da874b495fd62a6f4de28027e88325f 100644 (file)
@@ -67,6 +67,8 @@ public class FontInfo {
     /** Cache for Font instances. */
     private Map fontInstanceCache = new java.util.HashMap();
     
+    private FontEventListener eventListener;
+    
     /**
      * Main constructor
      */
@@ -77,6 +79,15 @@ public class FontInfo {
         this.usedFonts = new java.util.HashMap();
     }
 
+    /**
+     * Sets the font event listener that can be used to receive events about particular events
+     * in this class.
+     * @param listener the font event listener
+     */
+    public void setEventListener(FontEventListener listener) {
+        this.eventListener = listener;
+    }
+    
     /**
      * Checks if the font setup is valid (At least the ultimate fallback font 
      * must be registered.)
@@ -158,6 +169,9 @@ public class FontInfo {
     public void addMetrics(String name, FontMetrics metrics) {
         // add the given metrics as a font with the given name
 
+        if (metrics instanceof Typeface) {
+            ((Typeface)metrics).setEventListener(this.eventListener);
+        }
         this.fonts.put(name, metrics);
     }
 
@@ -364,8 +378,12 @@ public class FontInfo {
         }
         if (!loggedFontKeys.contains(replacedKey)) {
             loggedFontKeys.add(replacedKey);
-            log.warn("Font '" + replacedKey + "' not found. "
-                    + "Substituting with '" + newKey + "'.");
+            if (this.eventListener != null) {
+                this.eventListener.fontSubstituted(this, replacedKey, newKey);
+            } else {
+                log.warn("Font '" + replacedKey + "' not found. "
+                        + "Substituting with '" + newKey + "'.");
+            }
         }
     }
     
index 07b5be30557a18a85c9e5d5be9089337ed0e79af..5490e13f1487afe31ac9fbdef8fdbff56d084eef 100644 (file)
@@ -141,6 +141,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
                     throw new RuntimeException(ioex.getMessage());
                 }
             }
+            realFont.setEventListener(this.eventListener);
             isMetricsLoaded = true;
         }
     }
index ac12b761514e0e2709005f7e99132b08194bcb14..0a47d52ab82625b3668258ead9b1078f22d8b47a 100644 (file)
@@ -117,18 +117,22 @@ public class SingleByteFont extends CustomFont {
         }
         
         //Give up, character is not available
-        Character ch = new Character(c);
-        if (warnedChars == null) {
-            warnedChars = new java.util.HashSet();
-        }
-        if (warnedChars.size() < 8 && !warnedChars.contains(ch)) {
-            warnedChars.add(ch);
-            if (warnedChars.size() == 8) {
-                log.warn("Many requested glyphs are not available in font " + getFontName());
-            } else {
-                log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) 
-                        + ", " + Glyphs.charToGlyphName(c)
-                        + ") not available in font " + getFontName());
+        if (this.eventListener != null) {
+            this.eventListener.glyphNotAvailable(this, c, getFontName());
+        } else {
+            Character ch = new Character(c);
+            if (warnedChars == null) {
+                warnedChars = new java.util.HashSet();
+            }
+            if (warnedChars.size() < 8 && !warnedChars.contains(ch)) {
+                warnedChars.add(ch);
+                if (warnedChars.size() == 8) {
+                    log.warn("Many requested glyphs are not available in font " + getFontName());
+                } else {
+                    log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) 
+                            + ", " + Glyphs.charToGlyphName(c)
+                            + ") not available in font " + getFontName());
+                }
             }
         }
         return NOT_FOUND;
index 173d2e8a31c0e3faed660b0928ddd7173299f0d8..b6c78a3b003e3c00c17003f2992d02b3d4c24665 100644 (file)
@@ -30,6 +30,9 @@ public abstract class Typeface implements FontMetrics {
      */
     private long charMapOps = 0;
     
+    /** An optional event listener that receives events such as missing glyphs etc. */
+    protected FontEventListener eventListener;
+    
     /**
      * Get the encoding of the font.
      * @return the encoding
@@ -80,5 +83,14 @@ public abstract class Typeface implements FontMetrics {
         return getAscender(size);
     }
     
+    /**
+     * Sets the font event listener that can be used to receive events about particular events
+     * in this class.
+     * @param listener the font event listener
+     */
+    public void setEventListener(FontEventListener listener) {
+        this.eventListener = listener;
+    }
+    
 }
 
index 8207eb1409e8e8ffca7781d90fee9760efaf8933..b223ea7a1ac80176bcb7531fc1ab4eeee0c7c80f 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.fop.fonts.CustomFont;
 import org.apache.fop.fonts.EmbedFontInfo;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontCache;
+import org.apache.fop.fonts.FontEventListener;
 import org.apache.fop.fonts.FontLoader;
 import org.apache.fop.fonts.FontResolver;
 import org.apache.fop.fonts.FontTriplet;
@@ -52,6 +53,17 @@ public class FontInfoFinder {
     /** logging instance */
     private Log log = LogFactory.getLog(FontInfoFinder.class);
 
+    private FontEventListener eventListener;
+    
+    /**
+     * Sets the font event listener that can be used to receive events about particular events
+     * in this class.
+     * @param listener the font event listener
+     */
+    public void setEventListener(FontEventListener listener) {
+        this.eventListener = listener;
+    }
+    
     /**
      * Attempts to determine FontTriplets from a given CustomFont.
      * It seems to be fairly accurate but will probably require some tweaking over time
@@ -180,7 +192,6 @@ public class FontInfoFinder {
         
         // try to determine triplet information from font file
         CustomFont customFont = null;
-        
         if (fontUrl.toExternalForm().endsWith(".ttc")) {
             // Get a list of the TTC Font names
             List ttcNames = null; //List<String>
@@ -193,7 +204,9 @@ public class FontInfoFinder {
                 FontFileReader reader = new FontFileReader(in);
                 ttcNames = ttf.getTTCnames(reader);
             } catch (Exception e) {
-                log.error(e);
+                if (this.eventListener != null) {
+                    this.eventListener.fontLoadingErrorAtAutoDetection(this, fontFileURI, e);
+                }
             } finally {
                 IOUtils.closeQuietly(in);
             }
@@ -212,14 +225,16 @@ public class FontInfoFinder {
                 try {
                     ttfLoader = new TTFFontLoader(fontFileURI, fontName, resolver);
                     customFont = ttfLoader.getFont();
+                    if (this.eventListener != null) {
+                        customFont.setEventListener(this.eventListener);
+                    }
                 } catch (Exception e) {
-                    //TODO Too verbose (it's an error but we don't care if some fonts can't be loaded)
-                    //if (log.isErrorEnabled()) {
-                    log.error("Unable to load font file: " + embedUrl + ". Reason: " + e.getMessage());
-                    //}
                     if (fontCache != null) {
                         fontCache.registerFailedFont(embedUrl, fileLastModified);
                     }
+                    if (this.eventListener != null) {
+                        this.eventListener.fontLoadingErrorAtAutoDetection(this, embedUrl, e);
+                    }
                     continue;
                 }
                 EmbedFontInfo fi = fontInfoFromCustomFont(fontUrl, customFont, fontCache);
@@ -233,14 +248,16 @@ public class FontInfoFinder {
             // The normal case
             try {
                 customFont = FontLoader.loadFont(fontUrl, null, resolver);
+                if (this.eventListener != null) {
+                    customFont.setEventListener(this.eventListener);
+                }
             } catch (Exception e) {
-                //TODO Too verbose (it's an error but we don't care if some fonts can't be loaded)
-                //if (log.isErrorEnabled()) {
-                    log.error("Unable to load font file: " + embedUrl + ". Reason: " + e.getMessage());
-                //}
                 if (fontCache != null) {
                     fontCache.registerFailedFont(embedUrl, fileLastModified);
                 }
+                if (this.eventListener != null) {
+                    this.eventListener.fontLoadingErrorAtAutoDetection(this, embedUrl, e);
+                }
                 return null;
             }
             EmbedFontInfo fi = fontInfoFromCustomFont(fontUrl, customFont, fontCache);
@@ -250,7 +267,6 @@ public class FontInfoFinder {
                 return null;
             }
         }
-        
 
     }
 
index 81b3b4c07572df62bf94082479d826e2d03e22b9..be0995d8c116c71bd21805b72fca38f2ceb34254 100644 (file)
@@ -27,8 +27,11 @@ import java.util.Map;
 
 import org.apache.batik.bridge.BridgeContext;
 import org.apache.batik.bridge.GVTBuilder;
+import org.apache.batik.bridge.UserAgent;
 import org.apache.batik.dom.svg.SVGDOMImplementation;
 import org.apache.batik.gvt.GraphicsNode;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import org.apache.xmlgraphics.image.loader.Image;
 import org.apache.xmlgraphics.image.loader.ImageException;
@@ -40,7 +43,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
 import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
 import org.apache.xmlgraphics.util.UnitConv;
 
-import org.apache.fop.svg.SVGUserAgent;
+import org.apache.fop.svg.SimpleSVGUserAgent;
 
 /**
  * This ImageConverter converts SVG images to Java2D.
@@ -52,6 +55,9 @@ import org.apache.fop.svg.SVGUserAgent;
  */
 public class ImageConverterSVG2G2D extends AbstractImageConverter {
 
+    /** logger */
+    private static Log log = LogFactory.getLog(ImageConverterSVG2G2D.class);
+
     /** {@inheritDoc} */
     public Image convert(Image src, Map hints) throws ImageException {
         checkSourceFlavor(src);
@@ -67,9 +73,7 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter {
         if (ptm != null) {
             pxToMillimeter = (float)UnitConv.mm2in(ptm.doubleValue());
         }
-        SVGUserAgent ua = new SVGUserAgent(
-                pxToMillimeter,
-                new AffineTransform());
+        UserAgent ua = createBatikUserAgent(pxToMillimeter);
         GVTBuilder builder = new GVTBuilder();
         final BridgeContext ctx = new BridgeContext(ua);
 
@@ -107,6 +111,25 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter {
         return g2dImage;
     }
 
+    /**
+     * Creates a user agent for Batik. Override to provide your own user agent.
+     * @param pxToMillimeter the source resolution (in px per millimeter)
+     * @return the newly created user agent
+     */
+    protected SimpleSVGUserAgent createBatikUserAgent(float pxToMillimeter) {
+        return new SimpleSVGUserAgent(
+                pxToMillimeter,
+                new AffineTransform()) {
+
+            /** {@inheritDoc} */
+            public void displayMessage(String message) {
+                //TODO Refine and pipe through to caller
+                log.debug(message);
+            }
+            
+        };
+    }
+
     /** {@inheritDoc} */
     public ImageFlavor getSourceFlavor() {
         return ImageFlavor.XML_DOM;
index e59e06b6b07a92a538b6f0aaf608b7e94292e3ee..20557a644b8328dcffb86f3d598d53d615aefda3 100644 (file)
@@ -32,6 +32,7 @@ import org.w3c.dom.svg.SVGDocument;
 
 import org.apache.batik.bridge.BridgeContext;
 import org.apache.batik.bridge.UnitProcessor;
+import org.apache.batik.bridge.UserAgent;
 import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
 import org.apache.batik.dom.svg.SVGDOMImplementation;
 import org.apache.batik.dom.svg.SVGOMDocument;
@@ -46,7 +47,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
 import org.apache.xmlgraphics.image.loader.util.ImageUtil;
 import org.apache.xmlgraphics.util.MimeConstants;
 
-import org.apache.fop.svg.SVGUserAgent;
+import org.apache.fop.svg.SimpleSVGUserAgent;
 import org.apache.fop.util.UnclosableInputStream;
 
 /**
@@ -154,8 +155,15 @@ public class PreloaderSVG extends AbstractImagePreloader {
         private ImageInfo createImageInfo(String uri, ImageContext context, SVGDocument doc) {
             Element e = doc.getRootElement();
             float pxUnitToMillimeter = 25.4f / context.getSourceResolution(); 
-            SVGUserAgent userAg = new SVGUserAgent(pxUnitToMillimeter,
-                        new AffineTransform());
+            UserAgent userAg = new SimpleSVGUserAgent(pxUnitToMillimeter,
+                        new AffineTransform()) {
+
+                /** {@inheritDoc} */
+                public void displayMessage(String message) {
+                    log.debug(message);
+                }
+                
+            };
             BridgeContext ctx = new BridgeContext(userAg);
             UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, e);
 
index 65d537bcd4a76195cb363b615d09bc9e3b70c45f..4ef0579f251c8a952012e517059a7af36b8f33a2 100644 (file)
@@ -198,11 +198,11 @@ public abstract class AbstractBreaker {
     }
     
     /**
-     * Returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to notify about layout
-     * problems.
+     * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to
+     * notify about layout problems.
      * @return the listener instance or null if no notifications are needed
      */
-    protected PageBreakingAlgorithm.PageBreakingLayoutListener getLayoutListener() {
+    protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() {
         return null;
     }
     
@@ -320,7 +320,7 @@ public abstract class AbstractBreaker {
                 log.debug("PLM> start of algorithm (" + this.getClass().getName() 
                         + "), flow BPD =" + flowBPD);
                 PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(),
-                        getPageProvider(), getLayoutListener(),
+                        getPageProvider(), createLayoutListener(),
                         alignment, alignmentLast, footnoteSeparatorLength,
                         isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored());
                 int iOptPageCount;
index e8ca88c1cf5eb1bba26f1997e78ae4ef5bcf5074..f01f0e12f4c265ec2c4dcfde94260c847597868f 100644 (file)
@@ -35,7 +35,6 @@ import org.apache.fop.area.CTM;
 import org.apache.fop.area.Trait;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.Length;
-import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.flow.BlockContainer;
 import org.apache.fop.fo.properties.CommonAbsolutePosition;
 import org.apache.fop.traits.MinOptMax;
@@ -239,11 +238,11 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
 
         int availableIPD = referenceIPD - getIPIndents();
         if (getContentAreaIPD() > availableIPD) {
-            log.warn(FONode.decorateWithContextInfo(
-                    "The extent in inline-progression-direction (width) of a block-container is"
-                    + " bigger than the available space (" 
-                    + getContentAreaIPD() + "mpt > " + context.getRefIPD() + "mpt)", 
-                    getBlockContainerFO()));
+            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                    getBlockContainerFO().getUserAgent().getEventBroadcaster());
+            eventProducer.objectTooWide(this, getBlockContainerFO().getName(),
+                    getContentAreaIPD(), context.getRefIPD(),
+                    getBlockContainerFO().getLocator());
         }
         
         MinOptMax stackLimit = new MinOptMax(relDims.bpd);
@@ -378,10 +377,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
             */
 
             if (contentOverflows) {
-                log.warn("Contents overflow block-container viewport: clipping");
-                if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) {
-                    //TODO Throw layout exception
-                }
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getBlockContainerFO().getUserAgent().getEventBroadcaster());
+                boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); 
+                eventProducer.viewportOverflow(this, getBlockContainerFO().getName(),
+                        breaker.getOverflowAmount(), needClip(), canRecover,
+                        getBlockContainerFO().getLocator());
             }
         }
         addKnuthElementsForBorderPaddingAfter(returnList, true);
@@ -526,10 +527,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
     
             //TODO Maybe check for page overflow when autoHeight=true
             if (!autoHeight & (contentOverflows)) {
-                log.warn("Contents overflow block-container viewport: clipping");
-                if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) {
-                    //TODO Throw layout exception
-                }
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getBlockContainerFO().getUserAgent().getEventBroadcaster());
+                boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); 
+                eventProducer.viewportOverflow(this, getBlockContainerFO().getName(),
+                        breaker.getOverflowAmount(), needClip(), canRecover,
+                        getBlockContainerFO().getLocator());
             }
         }
 
@@ -602,7 +605,18 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
         }
         
         public boolean isOverflow() {
-            return !isEmpty() && (deferredAlg.getPageBreaks().size() > 1);
+            if (isEmpty()) {
+                return false;
+            } else {
+                return (deferredAlg.getPageBreaks().size() > 1)
+                    || (deferredAlg.totalWidth - deferredAlg.totalShrink) 
+                            > deferredAlg.getLineWidth();
+            }
+        }
+        
+        public int getOverflowAmount() {
+            return (deferredAlg.totalWidth - deferredAlg.totalShrink) 
+                - deferredAlg.getLineWidth();
         }
         
         protected LayoutManager getTopLevelLM() {
diff --git a/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java b/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java
new file mode 100644 (file)
index 0000000..c31a704
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.layoutmgr;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+
+/**
+ * Event producer interface for block-level layout managers.
+ */
+public interface BlockLevelEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static BlockLevelEventProducer get(EventBroadcaster broadcaster) {
+            return (BlockLevelEventProducer)broadcaster.getEventProducerFor(
+                    BlockLevelEventProducer.class);
+        }
+    }
+
+    /**
+     * The contents of a table-row are too big to fit in the constraints.
+     * @param source the event source
+     * @param row the row number
+     * @param effCellBPD the effective extent in block-progression direction of the cell
+     * @param maxCellBPD the maximum extent in block-progression direction of the cell
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void rowTooTall(Object source, int row, int effCellBPD, int maxCellBPD, Locator loc);
+    
+    /**
+     * Auto-table layout is not supported, yet.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity INFO
+     */
+    void tableFixedAutoWidthNotSupported(Object source, Locator loc);
+    
+    /**
+     * An formatting object is too wide.
+     * @param source the event source
+     * @param elementName the formatting object 
+     * @param effIPD the effective extent in inline-progression direction of the table contents
+     * @param maxIPD the maximum extent in inline-progression direction available
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void objectTooWide(Object source, String elementName, int effIPD, int maxIPD, Locator loc);
+        
+    /**
+     * An overconstrained geometry adjustment rule was triggered (5.3.4, XSL 1.0).
+     * @param source the event source
+     * @param elementName the formatting object 
+     * @param amount the amount of the adjustment (in mpt)
+     * @param loc the location of the error or null
+     * @event.severity INFO
+     */
+    void overconstrainedAdjustEndIndent(Object source, String elementName, int amount, Locator loc);
+    
+    /**
+     * Contents overflow a viewport.
+     * @param source the event source
+     * @param elementName the formatting object
+     * @param amount the amount by which the contents overflow (in mpt)
+     * @param clip true if the content will be clipped
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws LayoutException the layout error provoked by the method call
+     * @event.severity FATAL
+     */
+    void viewportOverflow(Object source, String elementName, 
+            int amount, boolean clip, boolean canRecover,
+            Locator loc) throws LayoutException;
+    
+    /**
+     * Contents overflow a region viewport.
+     * @param source the event source
+     * @param elementName the formatting object
+     * @param page the page number/name where the overflow happened
+     * @param amount the amount by which the contents overflow (in mpt)
+     * @param clip true if the content will be clipped
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws LayoutException the layout error provoked by the method call
+     * @event.severity FATAL
+     */
+    void regionOverflow(Object source, String elementName,
+            String page,
+            int amount, boolean clip, boolean canRecover,
+            Locator loc) throws LayoutException;
+    
+    /**
+     * Indicates that FOP doesn't support flows that are not mapped to region-body, yet.
+     * @param source the event source
+     * @param flowName the flow name
+     * @param masterName the page master name
+     * @param loc the location of the error or null
+     * @throws UnsupportedOperationException the layout error provoked by the method call
+     * @event.severity FATAL
+     */
+    void flowNotMappingToRegionBody(Object source, String flowName, String masterName,
+            Locator loc) throws UnsupportedOperationException;
+    
+    /**
+     * A page sequence master is exhausted.
+     * @param source the event source
+     * @param pageSequenceMasterName the name of the page sequence master
+     * @param canRecover indicates whether FOP can recover from this problem and continue working
+     * @param loc the location of the error or null
+     * @throws FOPException the error provoked by the method call
+     * @event.severity FATAL
+     */
+    void pageSequenceMasterExhausted(Object source, String pageSequenceMasterName,
+            boolean canRecover, Locator loc) throws FOPException;
+
+    /**
+     * No subsequences in page sequence master.
+     * @param source the event source
+     * @param pageSequenceMasterName the name of the page sequence master
+     * @param loc the location of the error or null
+     * @throws FOPException the error provoked by the method call
+     * @event.severity FATAL
+     */
+    void missingSubsequencesInPageSequenceMaster(Object source, String pageSequenceMasterName,
+            Locator loc) throws FOPException;
+    
+    /**
+     * No single-page-master matching in page sequence master.
+     * @param source the event source
+     * @param pageSequenceMasterName the name of the page sequence master
+     * @param pageMasterName the name of the page master not matching
+     * @param loc the location of the error or null
+     * @throws FOPException the error provoked by the method call
+     * @event.severity FATAL
+     */
+    void noMatchingPageMaster(Object source, String pageSequenceMasterName,
+            String pageMasterName, Locator loc) throws FOPException;
+    
+}
index 67ed1de9f41202e533531ccb6ad874d5d437bb28..cb6db6b012476c8f6a2f8f1a93fe65a951f51184 100644 (file)
@@ -201,6 +201,10 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager
         if (ipd < 0) {
             //5.3.4, XSL 1.0, Overconstrained Geometry
             log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj);
+            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                    getFObj().getUserAgent().getEventBroadcaster());
+            eventProducer.overconstrainedAdjustEndIndent(this,
+                    getFObj().getName(), ipd, getFObj().getLocator());
             endIndent += ipd;
             ipd = 0;
             //TODO Should we skip layout for a block that has ipd=0?
@@ -212,7 +216,7 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager
     /**
      * Sets the content area IPD by directly supplying the value. 
      * end-indent is adjusted based on overconstrained geometry rules, if necessary.
-     * 
+     * @param contentIPD the IPD of the content
      * @return the resulting content area IPD
      */
     protected int updateContentAreaIPDwithOverconstrainedAdjust(int contentIPD) {
@@ -220,6 +224,10 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager
         if (ipd < 0) {
             //5.3.4, XSL 1.0, Overconstrained Geometry
             log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj);
+            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                    getFObj().getUserAgent().getEventBroadcaster());
+            eventProducer.overconstrainedAdjustEndIndent(this,
+                    getFObj().getName(), ipd, getFObj().getLocator());
             endIndent += ipd;
         }
         setContentAreaIPD(contentIPD);
index 21856c781cd1f1e667696c60fc0510028f295d35..086d91c313ee63e09eec8cf6edc942b7adf894fb 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.layoutmgr;
 
 import java.awt.Dimension;
 import java.awt.Rectangle;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -46,6 +47,7 @@ import org.apache.fop.area.inline.Image;
 import org.apache.fop.area.inline.Viewport;
 import org.apache.fop.datatypes.FODimension;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExternalDocument;
 import org.apache.fop.layoutmgr.inline.ImageLayout;
@@ -140,17 +142,28 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan
                         pageIndex++;
                     }
                 } catch (URISyntaxException e) {
-                    log.error("Error parsing or constructing URIs based on URI: " + uri);
+                    getResourceEventProducer().uriError(this, uri, e,
+                            getExternalDocument().getLocator());
                     return;
                 }
             }
+        } catch (FileNotFoundException fnfe) {
+            getResourceEventProducer().imageNotFound(this, uri, fnfe,
+                    getExternalDocument().getLocator());
         } catch (IOException ioe) {
-            log.error("Image not available: " + uri, ioe);
+            getResourceEventProducer().imageIOError(this, uri, ioe,
+                    getExternalDocument().getLocator());
         } catch (ImageException ie) {
-            log.error("Error while inspecting image: " + uri + " (" + ie.getMessage() + ")");
+            getResourceEventProducer().imageError(this, uri, ie,
+                    getExternalDocument().getLocator());
         }
     }
 
+    private ResourceEventProducer getResourceEventProducer() {
+        return ResourceEventProducer.Provider.get(
+                getExternalDocument().getUserAgent().getEventBroadcaster());
+    }
+
     private void makePageForImage(ImageInfo info, ImageLayout layout) {
         this.imageLayout = layout;
         curPage = makeNewPage(false, false);
diff --git a/src/java/org/apache/fop/layoutmgr/LayoutException.java b/src/java/org/apache/fop/layoutmgr/LayoutException.java
new file mode 100644 (file)
index 0000000..350cc75
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.layoutmgr;
+
+import java.util.Locale;
+
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventFormatter;
+import org.apache.fop.events.EventExceptionManager.ExceptionFactory;
+
+/**
+ * Exception thrown by FOP if an unrecoverable layout error occurs. An example: An area overflows
+ * a viewport that has overflow="error-if-overflow".
+ * 
+ * @todo Discuss if this should become a checked exception.
+ */
+public class LayoutException extends RuntimeException {
+
+    private static final long serialVersionUID = 5157080040923740433L;
+    
+    private String localizedMessage;
+    private LayoutManager layoutManager;
+
+    /**
+     * Constructs a new layout exception with the specified detail message.
+     * @param message the detail message.
+     */
+    public LayoutException(String message) {
+        this(message, null);
+    }
+
+    /**
+     * Constructs a new layout exception with the specified detail message.
+     * @param message the detail message
+     * @param lm the layout manager that throws the exception
+     */
+    public LayoutException(String message, LayoutManager lm) {
+        super(message);
+        this.layoutManager = lm;
+    }
+
+    /**
+     * Sets the localized message for this exception.
+     * @param msg the localized message
+     */
+    public void setLocalizedMessage(String msg) {
+        this.localizedMessage = msg;
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalizedMessage() {
+        if (this.localizedMessage != null) {
+            return this.localizedMessage;
+        } else {
+            return super.getLocalizedMessage();
+        }
+    }
+
+    /**
+     * Returns the layout manager that detected the problem.
+     * @return the layout manager (or null)
+     */
+    public LayoutManager getLayoutManager() {
+        return this.layoutManager;
+    }
+    
+    /** Exception factory for {@link LayoutException}. */
+    public static class LayoutExceptionFactory implements ExceptionFactory {
+
+        /** {@inheritDoc} */
+        public Throwable createException(Event event) {
+            Object source = event.getSource();
+            LayoutManager lm = (source instanceof LayoutManager) ? (LayoutManager)source : null;
+            String msg = EventFormatter.format(event, Locale.ENGLISH);
+            LayoutException ex = new LayoutException(msg, lm);
+            if (!Locale.ENGLISH.equals(Locale.getDefault())) {
+                ex.setLocalizedMessage(EventFormatter.format(event));
+            }
+            return ex;
+        }
+        
+        /** {@inheritDoc} */
+        public Class getExceptionClass() {
+            return LayoutException.class;
+        }
+        
+    }    
+}
index 3e100cd50ea97d945390ec402b0c6e9a57857f68..d6be75758e9bb54b62daff349478c4791c4f6aba 100644 (file)
@@ -27,7 +27,6 @@ import org.apache.fop.area.Block;
 import org.apache.fop.area.Footnote;
 import org.apache.fop.area.PageViewport;
 import org.apache.fop.fo.Constants;
-import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.pagination.PageSequence;
 import org.apache.fop.fo.pagination.Region;
@@ -79,27 +78,25 @@ public class PageBreaker extends AbstractBreaker {
         return pslm.getPageProvider();
     }
     
-    /**
-     * {@inheritDoc}
-     */
-    protected PageBreakingLayoutListener getLayoutListener() {
+    /** {@inheritDoc} */
+    protected PageBreakingLayoutListener createLayoutListener() {
         return new PageBreakingLayoutListener() {
 
-            public void notifyOverflow(int part, FObj obj) {
+            public void notifyOverflow(int part, int amount, FObj obj) {
                 Page p = pageProvider.getPage(
                             false, part, PageProvider.RELTO_CURRENT_ELEMENT_LIST);
                 RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion(
                         Region.FO_REGION_BODY);
-                String err = FONode.decorateWithContextInfo(
-                        "Content of the region-body on page " 
-                        + p.getPageViewport().getPageNumberString() 
-                        + " overflows the available area in block-progression dimension.", 
-                        obj);
-                if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) {
-                    throw new RuntimeException(err);
-                } else {
-                    log.warn(err);
-                }
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        body.getUserAgent().getEventBroadcaster());
+
+                boolean canRecover = (body.getOverflow() != Constants.EN_ERROR_IF_OVERFLOW);
+                boolean needClip = (body.getOverflow() == Constants.EN_HIDDEN
+                        || body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW);
+                eventProducer.regionOverflow(this, body.getName(),
+                        p.getPageViewport().getPageNumberString(),
+                        amount, needClip, canRecover,
+                        body.getLocator());
             }
             
         };
@@ -297,7 +294,7 @@ public class PageBreaker extends AbstractBreaker {
         //Restart last page
         PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
                 getTopLevelLM(),
-                getPageProvider(), getLayoutListener(),
+                getPageProvider(), createLayoutListener(),
                 alg.getAlignment(), alg.getAlignmentLast(), 
                 footnoteSeparatorLength,
                 isPartOverflowRecoveryActivated(), false, false);
@@ -356,7 +353,7 @@ public class PageBreaker extends AbstractBreaker {
         //Restart last page
         PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
                 getTopLevelLM(),
-                getPageProvider(), getLayoutListener(),
+                getPageProvider(), createLayoutListener(),
                 alignment, Constants.EN_START, footnoteSeparatorLength,
                 isPartOverflowRecoveryActivated(),
                 pslm.getCurrentPV().getBodyRegion().getColumnCount());
index d98d29b5ce917e62169e89de48eeff5b9c629382..5e3d0a887ba7e00c6968c5de9e94db4d042899e7 100644 (file)
@@ -27,7 +27,6 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.fo.Constants;
-import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
 import org.apache.fop.traits.MinOptMax;
@@ -289,7 +288,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
 
     private void resetFootnotes(LinkedList elementLists) {
         for (int i = 0; i < elementLists.size(); i++) {
-            LinkedList removedList = (LinkedList) footnotesList.remove(footnotesList.size() - 1);
+            /*LinkedList removedList = (LinkedList)*/footnotesList.remove(footnotesList.size() - 1);
             lengthList.remove(lengthList.size() - 1);
 
             // update totalFootnotesLength
@@ -332,11 +331,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
                     actualWidth += allFootnotes;
                     insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
                     footnoteListIndex = footnotesList.size() - 1;
-                    footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
-                } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex))
+                    footnoteElementIndex
+                        = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
+                } else if (((canDeferOldFootnotes
+                                = checkCanDeferOldFootnotes(pageNode, elementIndex))
                             || newFootnotes)
-                           && (footnoteSplit = getFootnoteSplit(pageNode, getLineWidth() - actualWidth,
-                                                                canDeferOldFootnotes)) > 0) {
+                           && (footnoteSplit = getFootnoteSplit(pageNode,
+                                   getLineWidth() - actualWidth, canDeferOldFootnotes)) > 0) {
                     // it is allowed to break or even defer footnotes if either:
                     //  - there are new footnotes in the last piece of content, and
                     //    there is space to add at least a piece of the first one
@@ -356,7 +357,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
                     actualWidth += allFootnotes;
                     insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
                     footnoteListIndex = footnotesList.size() - 1;
-                    footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
+                    footnoteElementIndex
+                        = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
                 }
             } else {
                 // all footnotes have already been placed on previous pages
@@ -381,7 +383,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
      */
     private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) {
         return (noBreakBetween(node.position, contentElementIndex)
-                && deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes));
+                && deferredFootnotes(node.footnoteListIndex,
+                        node.footnoteElementIndex, node.totalFootnotes));
     }
 
     /**
@@ -455,7 +458,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
      * @param availableLength available space for footnotes
      * @param canDeferOldFootnotes
      */
-    private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) {
+    private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength,
+                boolean canDeferOldFootnotes) {
         return getFootnoteSplit(activeNode.footnoteListIndex,
                                 activeNode.footnoteElementIndex,
                                 activeNode.totalFootnotes,
@@ -520,7 +524,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
             }
 
             // try adding a split of the next note
-            noteListIterator = ((LinkedList) footnotesList.get(listIndex)).listIterator(elementIndex);
+            noteListIterator = ((LinkedList) footnotesList.get(listIndex))
+                    .listIterator(elementIndex);
 
             int prevSplitLength = 0;
             int prevIndex = -1;
@@ -754,14 +759,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         }
     }
     
-    private int getPartCount() {
-        if (pageBreaks == null) {
-            return 0;
-        } else {
-            return pageBreaks.size();
-        }
-    }
-    
     public void updateData1(int total, double demerits) {
     }
 
@@ -774,12 +771,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         if (difference + bestActiveNode.availableShrink < 0) {
             if (!autoHeight) {
                 if (layoutListener != null) {
-                    layoutListener.notifyOverflow(bestActiveNode.line - 1, getFObj());
-                } else if (log.isWarnEnabled()) {
-                    log.warn(FONode.decorateWithContextInfo(
-                            "Part/page " + (bestActiveNode.line - 1) 
-                            + " overflows the available area in block-progression dimension.", 
-                            getFObj()));
+                    layoutListener.notifyOverflow(bestActiveNode.line - 1, -difference, getFObj());
                 }
             }
         }
@@ -890,9 +882,10 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         /**
          * Issued when an overflow is detected
          * @param part the number of the part (page) this happens on
+         * @param amount the amount by which the area overflows (in mpt)
          * @param obj the root FO object where this happens
          */
-        void notifyOverflow(int part, FObj obj);
+        void notifyOverflow(int part, int amount, FObj obj);
         
     }
     
index e16c3396af966114d1480c20177179758e544abf..037f02094467ca88ff6a5890c225a075c4b924ae 100644 (file)
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.area.AreaTreeHandler;
 import org.apache.fop.fo.Constants;
@@ -74,6 +75,7 @@ public class PageProvider implements Constants {
 
     /**
      * Main constructor.
+     * @param ath the area tree handler
      * @param ps The page-sequence the provider operates on
      */
     public PageProvider(AreaTreeHandler ath, PageSequence ps) {
@@ -258,10 +260,10 @@ public class PageProvider implements Constants {
             if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
                 // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
                 // any region), but we don't support it yet.
-                throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName()
-                    + "' does not map to the region-body in page-master '"
-                    + spm.getMasterName() + "'.  FOP presently "
-                    + "does not support this.");
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        pageSeq.getUserAgent().getEventBroadcaster());
+                eventProducer.flowNotMappingToRegionBody(this,
+                        pageSeq.getMainFlow().getFlowName(), spm.getMasterName(), spm.getLocator());
             }
             Page page = new Page(spm, index, pageNumberString, isBlank);
             //Set unique key obtained from the AreaTreeHandler
index b1e41452793809d9f429c836b07cd06172013c1b..763ddf58d2983e6d45f77bf0c7c5cfb616aa622f 100644 (file)
@@ -29,10 +29,12 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.fop.area.Area;
 import org.apache.fop.area.Block;
 import org.apache.fop.area.RegionReference;
-import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.pagination.PageSequence;
 import org.apache.fop.fo.pagination.SideRegion;
 import org.apache.fop.fo.pagination.StaticContent;
+import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener;
 import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager;
 import org.apache.fop.layoutmgr.inline.TextLayoutManager;
 import org.apache.fop.traits.MinOptMax;
@@ -240,12 +242,17 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager {
         breaker.doLayout(targetBPD, autoHeight);
         if (breaker.isOverflow()) {
             if (!autoHeight) {
-                //Overflow handling
-                if (regionFO.getOverflow() == EN_ERROR_IF_OVERFLOW) {
-                    //TODO throw layout exception
-                }
-                log.warn(FONode.decorateWithContextInfo(
-                        "static-content overflows the available area.", fobj));
+                String page = getPSLM().getCurrentPage().getPageViewport().getPageNumberString();
+                
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getStaticContentFO().getUserAgent().getEventBroadcaster());
+                boolean canRecover = (regionFO.getOverflow() != EN_ERROR_IF_OVERFLOW); 
+                boolean needClip = (regionFO.getOverflow() == Constants.EN_HIDDEN
+                        || regionFO.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW);
+                eventProducer.regionOverflow(this, regionFO.getName(),
+                        page,
+                        breaker.getOverflowAmount(), needClip, canRecover,
+                        getStaticContentFO().getLocator());
             }
         }
     }
@@ -262,7 +269,7 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager {
         private StaticContentLayoutManager lm;
         private int displayAlign;
         private int ipd;
-        private boolean overflow = false;
+        private int overflow = 0;
         
         public StaticContentBreaker(StaticContentLayoutManager lm, int ipd, 
                 int displayAlign) {
@@ -288,9 +295,26 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager {
         }
 
         public boolean isOverflow() {
+            return (this.overflow != 0);
+        }
+        
+        public int getOverflowAmount() {
             return this.overflow;
         }
         
+        /** {@inheritDoc} */
+        protected PageBreakingLayoutListener createLayoutListener() {
+            return new PageBreakingLayoutListener() {
+
+                public void notifyOverflow(int part, int amount, FObj obj) {
+                    if (StaticContentBreaker.this.overflow == 0) {
+                        StaticContentBreaker.this.overflow = amount;
+                    }
+                }
+                
+            };
+        }
+
         protected LayoutManager getTopLevelLM() {
             return lm;
         }
@@ -340,9 +364,6 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager {
         
         protected void doPhase3(PageBreakingAlgorithm alg, int partCount, 
                 BlockSequence originalList, BlockSequence effectiveList) {
-            if (partCount > 1) {
-                overflow = true;
-            }
             //Rendering all parts (not just the first) at once for the case where the parts that 
             //overflow should be visible.
             alg.removeAllPageBreaks();
index ff7c5b3ced0ddf4c4ff55747b7311018f90aae9d..2af844c5c9f6de6e988741e1c4997fd5394dd432 100644 (file)
@@ -60,7 +60,6 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager
      */
     private static Log log = LogFactory.getLog(ContentLayoutManager.class);
 
-    private FOUserAgent userAgent;
     private Area holder;
     private int stackSize;
     private LayoutManager parentLM;
diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java
new file mode 100644 (file)
index 0000000..51d2720
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.layoutmgr.inline;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+
+/**
+ * Event producer interface for inline-level layout managers.
+ */
+public interface InlineLevelEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static InlineLevelEventProducer get(EventBroadcaster broadcaster) {
+            return (InlineLevelEventProducer)broadcaster.getEventProducerFor(
+                    InlineLevelEventProducer.class);
+        }
+    }
+
+    /**
+     * fo:leader with "use-content" but without children.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity ERROR
+     */
+    void leaderWithoutContent(Object source, Locator loc);
+    
+    /**
+     * A line overflows.
+     * @param source the event source
+     * @param line the number number
+     * @param overflowLength the length by which the content overflows the available space (in mpt)
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void lineOverflows(Object source, int line, int overflowLength, Locator loc);
+    
+}
index f4bcde96fe7c7165c974145576a0d86f0965e2e8..1e7c793dfa5d1fd06e3c551005bbc2e4582f3cba 100644 (file)
 
 package org.apache.fop.layoutmgr.inline;
 
+import java.util.LinkedList;
+import java.util.List;
+
 import org.apache.fop.area.Trait;
 import org.apache.fop.area.inline.FilledArea;
 import org.apache.fop.area.inline.InlineArea;
 import org.apache.fop.area.inline.Space;
 import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.flow.Leader;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontInfo;
@@ -41,10 +45,6 @@ import org.apache.fop.layoutmgr.PositionIterator;
 import org.apache.fop.layoutmgr.TraitSetter;
 import org.apache.fop.traits.MinOptMax;
 
-import java.util.List;
-import java.util.LinkedList;
-import org.apache.fop.fo.FObj;
-
 /**
  * LayoutManager for the fo:leader formatting object
  */
@@ -159,7 +159,9 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
             leaderArea = fa;
         } else if (fobj.getLeaderPattern() == EN_USECONTENT) {
             if (fobj.getChildNodes() == null) {
-                fobj.getLogger().error("Leader use-content with no content");
+                InlineLevelEventProducer eventProducer = InlineLevelEventProducer.Provider.get(
+                        getFObj().getUserAgent().getEventBroadcaster());
+                eventProducer.leaderWithoutContent(this, getFObj().getLocator());
                 return null;
             }
 
@@ -339,9 +341,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
         return returnList;
     }
 
-    /**
-     * {@inheritDoc} 
-     */
+    /** {@inheritDoc} */
     public int getBaseLength(int lengthBase, FObj fobj) {
         return getParent().getBaseLength(lengthBase, getParent().getFObj());
     }
index 9a818232c43f0f11f01b1ec79c1a73b8d430b995..1258fbe186d5d0d8f2e10b9685825f4b49fd3012 100644 (file)
@@ -34,7 +34,6 @@ import org.apache.fop.area.inline.InlineArea;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.fo.Constants;
-import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.flow.Block;
 import org.apache.fop.fo.properties.CommonHyphenation;
 import org.apache.fop.fonts.Font;
@@ -384,11 +383,12 @@ public class LineLayoutManager extends InlineStackingLayoutManager
             if (log.isWarnEnabled()) {
                 int lack = difference + bestActiveNode.availableShrink; 
                 if (lack < 0) {
+                    InlineLevelEventProducer eventProducer
+                        = InlineLevelEventProducer.Provider.get(
+                            getFObj().getUserAgent().getEventBroadcaster());
+                    eventProducer.lineOverflows(this, addedPositions + 1,
+                            -lack, getFObj().getLocator());
                     String textDiff = (lack < -50000 ? "more than 50 points" : (-lack) + "mpt");
-                    log.warn(FONode.decorateWithContextInfo(
-                            "Line " + (addedPositions + 1) 
-                            + " of a paragraph overflows the available area by "
-                            + textDiff + ".", getFObj()));
                 }
             }
             
@@ -1430,7 +1430,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager
                         auxCount++;
                     }
                 }
-                log.trace(" Word to hyphenate: " + sbChars.toString());
+                if (log.isTraceEnabled()) {
+                    log.trace(" Word to hyphenate: " + sbChars.toString());
+                }
                 // find hyphenation points
                 HyphContext hc = getHyphenContext(sbChars);
                 // ask each LM to hyphenate its word fragment
index 2ef2be9080b7d01edc7910ddd4d07ffc6948980b..bd032e6108b61269da9c4065c82c85cab81d3a1e 100644 (file)
@@ -25,9 +25,9 @@ import java.util.ListIterator;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.fop.datatypes.PercentBaseContext;
-import org.apache.fop.datatypes.Length;
 
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.datatypes.PercentBaseContext;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.flow.table.Table;
 import org.apache.fop.fo.flow.table.TableColumn;
@@ -77,12 +77,14 @@ public class ColumnSetup {
                 }
             }
             //Post-processing the list (looking for gaps)
+            //TODO The following block could possibly be removed
             int pos = 1;
             ListIterator ppIter = columns.listIterator();
             while (ppIter.hasNext()) {
                 TableColumn col = (TableColumn)ppIter.next();
                 if (col == null) {
-                    log.error("Found a gap in the table-columns at position " + pos);
+                    assert false; //Gaps are filled earlier by fo.flow.table.Table.finalizeColumns()
+                    //log.error("Found a gap in the table-columns at position " + pos);
                 }
                 pos++;
             }
@@ -100,7 +102,9 @@ public class ColumnSetup {
         if (index > size) {
             if (index > maxColIndexReferenced) {
                 maxColIndexReferenced = index;
-                if (!(size == 1 && getColumn(1).isImplicitColumn())) {
+                TableColumn col = getColumn(1);
+                if (!(size == 1 && col.isImplicitColumn())) {
+                    assert false; //TODO Seems to be removable as this is now done in the FO tree
                     log.warn(FONode.decorateWithContextInfo(
                             "There are fewer table-columns than are needed. "
                             + "Column " + index + " was accessed, but only "
@@ -193,8 +197,7 @@ public class ColumnSetup {
             if (colWidth != null) {
                 sumCols += colWidth.getValue(tlm);
                 if (colWidth instanceof TableColLength) {
-                    factors += 
-                        ((TableColLength) colWidth).getTableUnits();
+                    factors += ((TableColLength) colWidth).getTableUnits();
                 }
             }
         }
index 9c97ca8270f945bc8945ca266ca0aa380ee39af4..72e78b84f2596cca3944b4e5621db7f39114fb3c 100644 (file)
@@ -24,6 +24,7 @@ import java.util.LinkedList;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.flow.table.EffRow;
 import org.apache.fop.fo.flow.table.GridUnit;
@@ -32,6 +33,7 @@ import org.apache.fop.fo.flow.table.TableColumn;
 import org.apache.fop.fo.flow.table.TableRow;
 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.LengthRangeProperty;
+import org.apache.fop.layoutmgr.BlockLevelEventProducer;
 import org.apache.fop.layoutmgr.ElementListObserver;
 import org.apache.fop.layoutmgr.LayoutContext;
 import org.apache.fop.layoutmgr.MinOptMaxUtil;
@@ -193,7 +195,9 @@ class RowGroupLayoutManager {
             row.setHeight(rowHeights[rgi]);
             row.setExplicitHeight(explicitRowHeight);
             // TODO re-enable and improve after clarification
+            //See http://markmail.org/message/h25ycwwu7qglr4k4
 //            if (maxCellBPD > row.getExplicitHeight().max) {
+//old:
 //                log.warn(FONode.decorateWithContextInfo(
 //                        "The contents of row " + (row.getIndex() + 1) 
 //                        + " are taller than they should be (there is a"
@@ -203,10 +207,12 @@ class RowGroupLayoutManager {
 //                        + " to " + maxCellBPD + " millipoints, but the row shouldn't get"
 //                        + " any taller than " + row.getExplicitHeight() + " millipoints.", 
 //                        row.getTableRow()));
+//new (with events):
+//                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Factory.create(
+//                        tableRow.getUserAgent().getEventBroadcaster());
+//                eventProducer.rowTooTall(this, row.getIndex() + 1,
+//                        maxCellBPD, row.getExplicitHeight().max, tableRow.getLocator());
 //            }
-            if (log.isDebugEnabled()) {
-                log.debug("  height=" + rowHeights[rgi] + " explicit=" + explicitRowHeight);
-            }
         }
     }
 }
index d6bba5cb528e79fb9bda39120c25d1e53f3fff90..720ca5faabb8d911638c489e0c67927720d8b8dc 100644 (file)
@@ -26,6 +26,7 @@ import java.util.List;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.apache.fop.area.Area;
 import org.apache.fop.area.Block;
 import org.apache.fop.datatypes.LengthBase;
@@ -34,6 +35,7 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.flow.table.Table;
 import org.apache.fop.fo.flow.table.TableColumn;
+import org.apache.fop.layoutmgr.BlockLevelEventProducer;
 import org.apache.fop.layoutmgr.BlockStackingLayoutManager;
 import org.apache.fop.layoutmgr.BreakElement;
 import org.apache.fop.layoutmgr.ConditionalElementListener;
@@ -189,9 +191,9 @@ public class TableLayoutManager extends BlockStackingLayoutManager
             updateContentAreaIPDwithOverconstrainedAdjust(contentIPD);
         } else {
             if (!getTable().isAutoLayout()) {
-                log.info("table-layout=\"fixed\" and width=\"auto\", "
-                        + "but auto-layout not supported " 
-                        + "=> assuming width=\"100%\"");
+                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                        getTable().getUserAgent().getEventBroadcaster());
+                eventProducer.tableFixedAutoWidthNotSupported(this, getTable().getLocator());
             }
             updateContentAreaIPDwithOverconstrainedAdjust();
         }
@@ -204,11 +206,11 @@ public class TableLayoutManager extends BlockStackingLayoutManager
         }
         int availableIPD = referenceIPD - getIPIndents();
         if (getContentAreaIPD() > availableIPD) {
-            log.warn(FONode.decorateWithContextInfo(
-                    "The extent in inline-progression-direction (width) of a table is"
-                    + " bigger than the available space (" 
-                    + getContentAreaIPD() + "mpt > " + context.getRefIPD() + "mpt)", 
-                    getTable()));
+            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
+                    getTable().getUserAgent().getEventBroadcaster());
+            eventProducer.objectTooWide(this, getTable().getName(),
+                    getContentAreaIPD(), context.getRefIPD(),
+                    getTable().getLocator());
         }
         
         /* initialize unit to determine computed values
index f74699fd562e9bdb23d9c1625fa0739074f76062..2c47875d96be5ad19c362ed41d605c2753851424 100644 (file)
@@ -30,12 +30,14 @@ import org.w3c.dom.Document;
 
 import org.apache.batik.bridge.BridgeContext;
 import org.apache.batik.bridge.GVTBuilder;
+import org.apache.batik.dom.AbstractDocument;
 import org.apache.batik.dom.svg.SVGDOMImplementation;
 import org.apache.batik.gvt.GraphicsNode;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
 
 import org.apache.fop.render.RendererContext.RendererContextWrapper;
+import org.apache.fop.svg.SVGEventProducer;
 import org.apache.fop.svg.SVGUserAgent;
 
 /**
@@ -46,9 +48,6 @@ import org.apache.fop.svg.SVGUserAgent;
  */
 public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererContextConstants {
 
-    /** logging instance */
-    private static Log log = LogFactory.getLog(AbstractGenericSVGHandler.class);
-
     /** {@inheritDoc} */
     public void handleXML(RendererContext context, 
                 Document doc, String ns) throws Exception {
@@ -73,7 +72,7 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC
 
         //Prepare
         SVGUserAgent ua = new SVGUserAgent(
-                context.getUserAgent().getSourcePixelUnitToMillimeter(),
+                context.getUserAgent(),
                 new AffineTransform());
         GVTBuilder builder = new GVTBuilder();
         final BridgeContext ctx = new BridgeContext(ua);
@@ -83,7 +82,9 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC
         try {
             root = builder.build(ctx, doc);
         } catch (Exception e) {
-            log.error("SVG graphic could not be built: " + e.getMessage(), e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgNotBuilt(this, e, getDocumentURI(doc));
             return;
         }
 
@@ -114,6 +115,20 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC
                 x, y, wrappedContext.getWidth(), wrappedContext.getHeight()); 
     }
 
+    /**
+     * Gets the document URI from a Document instance if possible.
+     * @param doc the Document
+     * @return the URI or null
+     */
+    protected String getDocumentURI(Document doc) {
+        String docURI = null;
+        if (doc instanceof AbstractDocument) {
+            AbstractDocument level3Doc = (AbstractDocument)doc;
+            docURI = level3Doc.getDocumentURI();
+        }
+        return docURI;
+    }
+    
     /**
      * Override this method to update the renderer context if it needs special settings for
      * certain conditions.
index 9e13476f64c3015ff2d3c8acb1d0e1fccc41af84..b38d973c543d3c2162b83123df15f579a56895ac 100644 (file)
@@ -31,6 +31,7 @@ import org.w3c.dom.Document;
 import org.apache.batik.parser.AWTTransformProducer;
 
 import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.util.QName;
 
 import org.apache.fop.area.Area;
 import org.apache.fop.area.Block;
@@ -45,7 +46,6 @@ import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.fonts.FontMetrics;
 import org.apache.fop.traits.BorderProps;
-import org.apache.fop.util.QName;
 
 /**
  * Abstract base class for renderers like PDF and PostScript where many painting operations
index 32c4b33c4b5d233cf122d87dfb8889c18efe43e8..ca3d007d0238a49c824f76819e3ef01f9a277575 100644 (file)
@@ -67,6 +67,7 @@ import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.Viewport;
 import org.apache.fop.area.inline.WordArea;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fonts.FontInfo;
 
@@ -113,21 +114,15 @@ public abstract class AbstractRenderer
     
     private Set warnedXMLHandlers;
     
-    /**
-     *  {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public abstract void setupFontInfo(FontInfo fontInfo);
 
-    /**
-     *  {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void setUserAgent(FOUserAgent agent) {
         userAgent = agent;
     }
 
-    /**
-     *  @return the associated <code>FOUserAgent</code>
-     */
+    /** {@inheritDoc} */
     public FOUserAgent getUserAgent() {
         return userAgent;
     }
@@ -797,10 +792,11 @@ public abstract class AbstractRenderer
                     = new XMLHandlerConfigurator(userAgent);
                 configurator.configure(ctx, namespace);
                 handler.handleXML(ctx, doc, namespace);
-            } catch (Throwable t) {
+            } catch (Exception e) {
                 // could not handle document
-                log.error("Some XML content will be ignored. "
-                        + "Could not render XML", t);
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        ctx.getUserAgent().getEventBroadcaster());
+                eventProducer.foreignXMLProcessingError(this, doc, namespace, e);
             }
         } else {
             if (warnedXMLHandlers == null) {
@@ -809,8 +805,9 @@ public abstract class AbstractRenderer
             if (!warnedXMLHandlers.contains(namespace)) {
                 // no handler found for document
                 warnedXMLHandlers.add(namespace);
-                log.warn("Some XML content will be ignored. "
-                        + "No handler defined for XML: " + namespace);
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        ctx.getUserAgent().getEventBroadcaster());
+                eventProducer.foreignXMLNoHandler(this, doc, namespace);
             }
         }
     }
index b40eec0cffcf0f971d1d6982956a6218612addf6..03b4582f7069e5e59120a7021e246b7a49f1c2e1 100644 (file)
@@ -88,6 +88,12 @@ public interface Renderer {
      */
     void setUserAgent(FOUserAgent agent);
 
+    /**
+     * Returns the associated user agent.
+     * @return the user agent
+     */
+    FOUserAgent getUserAgent();
+    
     /**
      * Set up the given FontInfo.
      *
diff --git a/src/java/org/apache/fop/render/RendererEventProducer.java b/src/java/org/apache/fop/render/RendererEventProducer.java
new file mode 100644 (file)
index 0000000..365c8f4
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render;
+
+import java.io.IOException;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+
+/**
+ * Event producer interface for rendering-specific events.
+ */
+public interface RendererEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static RendererEventProducer get(EventBroadcaster broadcaster) {
+            return (RendererEventProducer)broadcaster.getEventProducerFor(
+                    RendererEventProducer.class);
+        }
+    }
+
+    /**
+     * I/O error while writing target file.
+     * @param source the event source
+     * @param ioe the original I/O error
+     * @event.severity ERROR
+     */
+    void ioError(Object source, IOException ioe);
+}
diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.java b/src/java/org/apache/fop/render/afp/AFPEventProducer.java
new file mode 100644 (file)
index 0000000..615c54c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.afp;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Event producer interface for AFP-specific events.
+ */
+public interface AFPEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static AFPEventProducer get(EventBroadcaster broadcaster) {
+            return (AFPEventProducer)broadcaster.getEventProducerFor(
+                    AFPEventProducer.class);
+        }
+    }
+
+    /** Event model factory for AFP. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * Warn about using default font setup.
+     * @param source the event source
+     * @event.severity WARN
+     */
+    void warnDefaultFontSetup(Object source);
+    
+}
diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.xml b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml
new file mode 100644 (file)
index 0000000..8eec9b6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="org.apache.fop.render.afp.AFPEventProducer.warnDefaultFontSetup">No AFP fonts configured. Using default setup.</message>
+</catalogue>
index 20587349375145b762e251209010fb89566642a7..83b8048f90f3ac15eab868785adb208fac950361 100644 (file)
@@ -70,6 +70,7 @@ import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fonts.FontInfo;
@@ -291,7 +292,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer {
                 }
             }
         } else {
-            log.warn("No AFP fonts configured - using default setup");
+            AFPEventProducer eventProducer = AFPEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.warnDefaultFontSetup(this);
         }
         if (this.fontInfo.fontLookup("sans-serif", "normal", 400) == null) {
             CharacterSet cs  = new FopCharacterSet("T1V10500", "Cp500", "CZH200  ",
@@ -909,7 +912,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer {
         RendererContext context;
         context = super.createRendererContext(x, y, width, height, foreignAttributes);
         context.setProperty(AFPRendererContextConstants.AFP_GRAYSCALE,
-                new Boolean(!this.colorImages));
+                Boolean.valueOf(!this.colorImages));
         return context;
     }
 
@@ -1014,13 +1017,17 @@ public class AFPRenderer extends AbstractPathOrientedRenderer {
                 }
 
             } catch (ImageException ie) {
-                log.error("Error while processing image: "
-                        + (info != null ? info.toString() : uri), ie);
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
             } catch (FileNotFoundException fe) {
-                log.error(fe.getMessage());
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
             } catch (IOException ioe) {
-                log.error("I/O error while processing image: "
-                        + (info != null ? info.toString() : uri), ioe);
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
             }
             
             /*
@@ -1195,7 +1202,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer {
                         image.getWidth(), image.getHeight(), this.bitsPerPixel);
             }
         } catch (IOException ioe) {
-            log.error("Error while serializing bitmap: " + ioe.getMessage(), ioe);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageWritingError(this, ioe);
         }
     }
 
@@ -1305,8 +1314,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer {
         } catch (Throwable ex) {
             encoding = AFPConstants.EBCIDIC_ENCODING;
             log.warn(
-                "renderText():: Error getting encoding for font "
-                + " - using default encoding "
+                "renderText():: Error getting encoding for font '"
+                + tf.getFullName()
+                + "' - using default encoding "
                 + encoding);
         }
 
diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java
new file mode 100644 (file)
index 0000000..7b26d07
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.bitmap;
+
+import java.io.IOException;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Event producer interface for events generated by the bitmap renderers.
+ */
+public interface BitmapRendererEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static BitmapRendererEventProducer get(EventBroadcaster broadcaster) {
+            return (BitmapRendererEventProducer)broadcaster.getEventProducerFor(
+                    BitmapRendererEventProducer.class);
+        }
+    }
+
+    /** Event model factory for this event producer. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * No filename information available. Stopping early after the first page.
+     * @param source the event source
+     * @event.severity WARN
+     */
+    void stoppingAfterFirstPageNoFilename(Object source);
+    
+    /**
+     * Image writer does not support multiple images. Only the first page has been produced.
+     * @param source the event source
+     * @event.severity WARN
+     */
+    void stoppingAfterFirstPageNoMultiWriter(Object source);
+    
+    /**
+     * No ImageWriter found.
+     * @param source the event source
+     * @param mime the target MIME type
+     * @throws IOException the I/O error provoked by the method call
+     * @event.severity FATAL
+     */
+    void noImageWriterFound(Object source, String mime) throws IOException;
+}
diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml
new file mode 100644 (file)
index 0000000..a05af3e
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoFilename">No filename information available. Stopping early after the first page.</message>
+  <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoMultiWriter">Image writer does not support multiple images. Only the first page has been produced.</message>
+  <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.noImageWriterFound">Could not get an ImageWriter to produce "{mime}". The most likely explanation for this is a class loading problem.</message>
+</catalogue>
index bedd2c49910c3652eb729b8d8c8551fafbb89f01..8613ef7b84c36f6ff796c63620e6c7e64cd3f280 100644 (file)
@@ -23,12 +23,12 @@ import java.awt.image.RenderedImage;
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.apache.commons.io.IOUtils;
+
 import org.apache.xmlgraphics.image.writer.ImageWriter;
 import org.apache.xmlgraphics.image.writer.ImageWriterParams;
 import org.apache.xmlgraphics.image.writer.ImageWriterRegistry;
 
-import org.apache.commons.io.IOUtils;
-
 import org.apache.fop.apps.MimeConstants;
 import org.apache.fop.area.PageViewport;
 import org.apache.fop.render.java2d.Java2DRenderer;
@@ -74,8 +74,10 @@ public class PNGRenderer extends Java2DRenderer {
 
             OutputStream os = getCurrentOutputStream(i);
             if (os == null) {
-                log.warn("No filename information available."
-                        + " Stopping early after the first page.");
+                BitmapRendererEventProducer eventProducer
+                    = BitmapRendererEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                eventProducer.stoppingAfterFirstPageNoFilename(this);
                 break;
             }
             try {
@@ -104,9 +106,10 @@ public class PNGRenderer extends Java2DRenderer {
         // Encode PNG image
         ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType());
         if (writer == null) {
-            throw new IOException("Could not get an ImageWriter to produce " 
-                    + getMimeType() + ". The most likely explanation for this is a class"
-                    + " loading problem.");
+            BitmapRendererEventProducer eventProducer
+                = BitmapRendererEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+            eventProducer.noImageWriterFound(this, getMimeType());
         }
         if (log.isDebugEnabled()) {
             log.debug("Writing image using " + writer.getClass().getName());
index 4f64e45e1c918fa45c7d9d04b5e94ad3396c4c27..9291427d2a8efc529b5c6d43e27dccb4544ae786 100644 (file)
@@ -129,7 +129,10 @@ public class TIFFRenderer extends Java2DRenderer {
         // Creates writer
         ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType());
         if (writer == null) {
-            throw new NullPointerException("No ImageWriter for " + getMimeType() + " available!");
+            BitmapRendererEventProducer eventProducer
+                = BitmapRendererEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+            eventProducer.noImageWriterFound(this, getMimeType());
         }
         if (writer.supportsMultiImageWriter()) {
             MultiImageWriter multiWriter = writer.createMultiImageWriter(outputStream);
@@ -145,8 +148,10 @@ public class TIFFRenderer extends Java2DRenderer {
         } else {
             writer.writeImage((RenderedImage) pageImagesItr.next(), outputStream, writerParams);
             if (pageImagesItr.hasNext()) {
-                log.error("Image encoder does not support multiple images. Only the first page"
-                        + " has been produced.");
+                BitmapRendererEventProducer eventProducer
+                    = BitmapRendererEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                eventProducer.stoppingAfterFirstPageNoFilename(this);
             }
         }
 
index 583d2ad2f550c4b4a4d06851ada23c4117ad69eb..0ffe3307ad0250cd47e3374ed7a049b3f8d22294 100644 (file)
@@ -66,6 +66,7 @@ import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontInfo;
@@ -930,13 +931,17 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
                         pos, foreignAttributes);
             }
         } catch (ImageException ie) {
-            log.error("Error while processing image: "
-                    + (info != null ? info.toString() : uri), ie);
-        } catch (FileNotFoundException fnfe) {
-            log.error(fnfe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
+        } catch (FileNotFoundException fe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
         } catch (IOException ioe) {
-            log.error("I/O error while processing image: "
-                    + (info != null ? info.toString() : uri), ioe);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
         }
     }
 
index 51b458cfe0d793f438178703f7ca5b61b209e632..64ac823fcbd9de7208887e1f02ecfccdb5db5e9d 100644 (file)
@@ -23,26 +23,25 @@ import java.awt.geom.AffineTransform;
 
 import org.w3c.dom.Document;
 
-import org.apache.fop.render.Renderer;
-import org.apache.fop.render.XMLHandler;
-import org.apache.fop.render.RendererContext;
-import org.apache.fop.svg.SVGUserAgent;
-
-// Commons-Logging
+import org.apache.batik.bridge.BridgeContext;
+import org.apache.batik.bridge.GVTBuilder;
+import org.apache.batik.gvt.GraphicsNode;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.apache.batik.bridge.GVTBuilder;
-import org.apache.batik.bridge.BridgeContext;
-import org.apache.batik.dom.svg.SVGDOMImplementation;
-import org.apache.batik.gvt.GraphicsNode;
+import org.apache.fop.render.AbstractGenericSVGHandler;
+import org.apache.fop.render.Renderer;
+import org.apache.fop.render.RendererContext;
+import org.apache.fop.svg.SVGEventProducer;
+import org.apache.fop.svg.SVGUserAgent;
 
 /**
  * Java2D XML handler for SVG (uses Apache Batik).
  * This handler handles XML for foreign objects when rendering to Java2D.
  * The properties from the Java2D renderer are subject to change.
  */
-public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConstants {
+public class Java2DSVGHandler extends AbstractGenericSVGHandler
+            implements Java2DRendererContextConstants {
 
     /** logging instance */
     private static Log log = LogFactory.getLog(Java2DSVGHandler.class);
@@ -54,16 +53,6 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta
         //nop
     }
 
-    /** {@inheritDoc} */
-    public void handleXML(RendererContext context, 
-                Document doc, String ns) throws Exception {
-        Java2DInfo pdfi = getJava2DInfo(context);
-
-        if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) {
-            renderSVGDocument(context, doc, pdfi);
-        }
-    }
-
     /**
      * Get the pdf information from the render context.
      *
@@ -106,23 +95,18 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta
         }
     }
     
-    /**
-     * Render the svg document.
-     * @param context the renderer context
-     * @param doc the svg document
-     * @param info the pdf information of the current context
-     */
+    /** {@inheritDoc} */
     protected void renderSVGDocument(RendererContext context,
-                                     Document doc,
-                                     Java2DInfo info) {
+                                     Document doc) {
+        Java2DInfo info = getJava2DInfo(context);
+        if (log.isDebugEnabled()) {
+            log.debug("renderSVGDocument(" + context + ", " + doc + ", " + info + ")");
+        }
 
-        log.debug("renderSVGDocument(" + context + ", " + doc + ", " + info + ")");
-        
         int x = info.currentXPosition;
         int y = info.currentYPosition;
         
-        float ptom = context.getUserAgent().getSourcePixelUnitToMillimeter();
-        SVGUserAgent ua = new SVGUserAgent(ptom, new AffineTransform());
+        SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
         
         GVTBuilder builder = new GVTBuilder();
         BridgeContext ctx = new BridgeContext(ua);
@@ -131,7 +115,9 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta
         try {
             root = builder.build(ctx, doc);
         } catch (Exception e) {
-            log.error("SVG graphic could not be built: " + e.getMessage(), e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgNotBuilt(this, e, getDocumentURI(doc));
             return;
         }
         
@@ -158,7 +144,9 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta
         try {
             root.paint(info.state.getGraph());
         } catch (Exception e) {
-            log.error("Error while painting SVG", e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgRenderingError(this, e, getDocumentURI(doc));
         }
         
         info.state.getGraph().setTransform(origTransform);
@@ -169,9 +157,4 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta
         return (renderer instanceof Java2DRenderer);
     }
 
-
-    /** {@inheritDoc} */
-    public String getNamespace() {
-        return SVGDOMImplementation.SVG_NAMESPACE_URI;
-    }
 }
diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.java b/src/java/org/apache/fop/render/pcl/PCLEventProducer.java
new file mode 100644 (file)
index 0000000..3e72de2
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pcl;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Event producer interface for events generated by the PCL renderer.
+ */
+public interface PCLEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static PCLEventProducer get(EventBroadcaster broadcaster) {
+            return (PCLEventProducer)broadcaster.getEventProducerFor(
+                    PCLEventProducer.class);
+        }
+    }
+
+    /** Event model factory for this event producer. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * Paper type could not be determined. Falling back to another.
+     * @param source the event source
+     * @param pageWidth the page width (in millipoints)
+     * @param pageHeight the page height (in millipoints)
+     * @param fallbackPaper the paper type that will be used instead
+     * @event.severity WARN
+     */
+    void paperTypeUnavailable(Object source, long pageWidth, long pageHeight, String fallbackPaper);
+    
+}
diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml
new file mode 100644 (file)
index 0000000..a3b36fd
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="org.apache.fop.render.pcl.PCLEventProducer.paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message>
+</catalogue>
index 3eb8ec42559ad511f44b7027e16827dd310c3d48..5ca9a8bf9edc33d298610eb08dad250cedb76f15 100644 (file)
@@ -43,9 +43,11 @@ import java.text.DecimalFormatSymbols;
 import java.util.Locale;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.apache.fop.util.UnitConv;
+
 import org.apache.xmlgraphics.image.GraphicsUtil;
 
+import org.apache.fop.util.UnitConv;
+
 /**
  * This class provides methods for generating PCL print files.
  */
@@ -355,16 +357,16 @@ public class PCLGenerator {
         if (usePCLShades 
                 || Color.black.equals(col)
                 || Color.white.equals(col)) {
-            writeCommand("*c" + formatDouble4(w / 100) + "h" 
-                              + formatDouble4(h / 100) + "V");
+            writeCommand("*c" + formatDouble4(w / 100.0) + "h" 
+                              + formatDouble4(h / 100.0) + "V");
             int lineshade = convertToPCLShade(col);
             writeCommand("*c" + lineshade + "G");
             writeCommand("*c2P"); //Shaded fill
         } else {
             defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4);
 
-            writeCommand("*c" + formatDouble4(w / 100) + "h" 
-                              + formatDouble4(h / 100) + "V");
+            writeCommand("*c" + formatDouble4(w / 100.0) + "h" 
+                              + formatDouble4(h / 100.0) + "V");
             writeCommand("*c32G");
             writeCommand("*c4P"); //User-defined pattern
         }
index 1d606e9196cdfc97329acb7a7399a097669b70b7..b89fba9c193ad89ae0260fa8212abf8715797aa5 100644 (file)
@@ -58,6 +58,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
 import org.apache.xmlgraphics.image.loader.util.ImageUtil;
 import org.apache.xmlgraphics.java2d.GraphicContext;
 import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
+import org.apache.xmlgraphics.util.QName;
 
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.MimeConstants;
@@ -77,6 +78,7 @@ import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.Viewport;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontInfo;
@@ -85,12 +87,12 @@ import org.apache.fop.render.Graphics2DAdapter;
 import org.apache.fop.render.PrintRenderer;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.RendererContextConstants;
+import org.apache.fop.render.RendererEventProducer;
 import org.apache.fop.render.java2d.FontMetricsMapper;
 import org.apache.fop.render.java2d.FontSetup;
 import org.apache.fop.render.java2d.Java2DRenderer;
 import org.apache.fop.render.pcl.extensions.PCLElementMapping;
 import org.apache.fop.traits.BorderProps;
-import org.apache.fop.util.QName;
 import org.apache.fop.util.UnitConv;
 
 /* Note:
@@ -208,7 +210,9 @@ public class PCLRenderer extends PrintRenderer {
      */
     protected void handleIOTrouble(IOException ioe) {
         if (!ioTrouble) {
-            log.error("Error while writing to target file", ioe);
+            RendererEventProducer eventProducer = RendererEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.ioError(this, ioe);
             ioTrouble = true;
         }
     }
@@ -417,11 +421,15 @@ public class PCLRenderer extends PrintRenderer {
         
         if (this.currentPageDefinition == null) {
             this.currentPageDefinition = PCLPageDefinition.getDefaultPageDefinition();
-            log.warn("Paper type could not be determined. Falling back to: " 
-                    + this.currentPageDefinition.getName());
+            PCLEventProducer eventProducer = PCLEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.paperTypeUnavailable(this, pagewidth, pageheight,
+                    this.currentPageDefinition.getName());
+        }
+        if (log.isDebugEnabled()) {
+            log.debug("page size: " + currentPageDefinition.getPhysicalPageSize());
+            log.debug("logical page: " + currentPageDefinition.getLogicalPageRect());
         }
-        log.debug("page size: " + currentPageDefinition.getPhysicalPageSize());
-        log.debug("logical page: " + currentPageDefinition.getLogicalPageRect());
         if (this.currentPageDefinition.isLandscapeFormat()) {
             gen.writeCommand("&l1O"); //Orientation
         } else {
@@ -1107,12 +1115,17 @@ public class PCLRenderer extends PrintRenderer {
             }
 
         } catch (ImageException ie) {
-            log.error("Error while processing image: "
-                    + (info != null ? info.toString() : uri), ie);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
         } catch (FileNotFoundException fe) {
-            log.error(fe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
         } catch (IOException ioe) {
-            handleIOTrouble(ioe);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
         }
     }
 
index 62d4bcaa4e0983116f56f7dbbe730d0a248595e8..422b9d51d48c33d2f982819717b882d85ac5f5e4 100644 (file)
 
 package org.apache.fop.render.pcl;
 
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.render.RendererContext;
-import org.apache.fop.util.QName;
 
 /**
  * Wrapper on the RendererContext to access the information structure for drawing 
diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java
new file mode 100644 (file)
index 0000000..f8b1bbb
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Event producer interface for events generated by the PDF renderer.
+ */
+public interface PDFEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static PDFEventProducer get(EventBroadcaster broadcaster) {
+            return (PDFEventProducer)broadcaster.getEventProducerFor(
+                    PDFEventProducer.class);
+        }
+    }
+
+    /** Event model factory for this event producer. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * Some link targets haven't been fully resolved.
+     * @param source the event source
+     * @param count the number of unresolved links
+     * @event.severity WARN
+     */
+    void nonFullyResolvedLinkTargets(Object source, int count);
+    
+}
diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
new file mode 100644 (file)
index 0000000..fd57d50
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="org.apache.fop.render.pdf.PDFEventProducer.nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message>
+</catalogue>
index 268791ac24822700c3e74a3792dfae94faa63b1a..936bf438457c799e12037c4592c58c48d48add64 100644 (file)
@@ -73,6 +73,7 @@ import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fo.extensions.xmp.XMPMetadata;
@@ -490,13 +491,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
                 PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
                 finishIDGoTo(gt, defaultPos);
             }
-            boolean one = count == 1;
-            String pl = one ? ""    : "s";
-            String ww = one ? "was" : "were";
-            String ia = one ? "is"  : "are";
-            log.warn("" + count + " link target" + pl + " could not be fully resolved and "
-                        + ww + " now point to the top of the page or "
-                        + ia + " dysfunctional.");  // dysfunctional if pageref is null
+            PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.nonFullyResolvedLinkTargets(this, count);
+            // dysfunctional if pageref is null
         }
     }
 
@@ -555,16 +553,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
 
     private void renderDestination(DestinationData dd) {
         String targetID = dd.getIDRef();
-        if (targetID != null && targetID.length() > 0) {
-            PageViewport pv = dd.getPageViewport();
-            if (pv == null) {
-                log.warn("Unresolved destination item received: " + dd.getIDRef());
-            }
+        if (targetID == null || targetID.length() == 0) {
+            throw new IllegalArgumentException("DestinationData must contain a ID reference");
+        }
+        PageViewport pv = dd.getPageViewport();
+        if (pv != null) {
             PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey());
             pdfDoc.getFactory().makeDestination(
                     dd.getIDRef(), gt.makeReference());
         } else {
-            log.warn("DestinationData item with null or empty IDRef received.");
+            //Warning already issued by AreaTreeHandler (debug level is sufficient)
+            log.debug("Unresolved destination item received: " + dd.getIDRef());
         }
     }
 
@@ -584,22 +583,22 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
         PDFOutline pdfOutline = null;
 
         String targetID = bookmarkItem.getIDRef();
-        if (targetID != null && targetID.length() > 0) {
-            PageViewport pv = bookmarkItem.getPageViewport();
-            if (pv != null) {
-                String pvKey = pv.getKey();
-                PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
-                // create outline object:
-                PDFOutline parent = parentBookmarkItem != null
-                    ? parentBookmarkItem
-                    : pdfDoc.getOutlineRoot();
-                pdfOutline = pdfDoc.getFactory().makeOutline(parent,
-                        bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
-            } else {
-                log.warn("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
-            }
+        if (targetID == null || targetID.length() == 0) {
+            throw new IllegalArgumentException("DestinationData must contain a ID reference");
+        }
+        PageViewport pv = bookmarkItem.getPageViewport();
+        if (pv != null) {
+            String pvKey = pv.getKey();
+            PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
+            // create outline object:
+            PDFOutline parent = parentBookmarkItem != null
+                ? parentBookmarkItem
+                : pdfDoc.getOutlineRoot();
+            pdfOutline = pdfDoc.getFactory().makeOutline(parent,
+                    bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
         } else {
-            log.warn("Bookmark item with null or empty IDRef received.");
+            //Warning already issued by AreaTreeHandler (debug level is sufficient)
+            log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
         }
 
         for (int i = 0; i < bookmarkItem.getCount(); i++) {
@@ -1368,15 +1367,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
                 if (annotsAllowed) {
                     action = getPDFGoToForID(idRef, pvKey);
                 }
-            } else if (pvKeyOK) {
-                log.warn("Internal link trait with PageViewport key " + pvKey 
-                         + " contains no ID reference.");
-            } else if (idRefOK) {
-                log.warn("Internal link trait with ID reference " + idRef 
-                         + " contains no PageViewport key.");
             } else {
-                log.warn("Internal link trait received with neither PageViewport key"
-                         + " nor ID reference.");
+                //Warnings already issued by AreaTreeHandler
             }
         }
 
@@ -1671,7 +1663,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
                             x, y, posInt.width, posInt.height, foreignAttributes);
                     handler.generateImage(context, img, origin, posInt);
                 } catch (IOException ioe) {
-                    log.error("I/O error while handling image: " + info, ioe);
+                    ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                    eventProducer.imageWritingError(this, ioe);
                     return;
                 }
             } else {
@@ -1680,13 +1674,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
                             + info + " (" + img.getClass().getName() + ")");
             }
         } catch (ImageException ie) {
-            log.error("Error while processing image: "
-                    + (info != null ? info.toString() : uri), ie);
-        } catch (FileNotFoundException fnfe) {
-            log.error(fnfe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
+        } catch (FileNotFoundException fe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
         } catch (IOException ioe) {
-            log.error("I/O error while processing image: "
-                    + (info != null ? info.toString() : uri), ioe);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
         }
 
         // output new data
@@ -1810,18 +1808,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
         return MIME_TYPE;
     }
     
+    /**
+     * Sets the PDF/A mode for the PDF renderer.
+     * @param mode the PDF/A mode
+     */
     public void setAMode(PDFAMode mode) {
         this.pdfAMode = mode;
     }
 
+    /**
+     * Sets the PDF/X mode for the PDF renderer.
+     * @param mode the PDF/X mode
+     */
     public void setXMode(PDFXMode mode) {
         this.pdfXMode = mode;        
     }
 
+    /**
+     * Sets the output color profile for the PDF renderer.
+     * @param outputProfileURI the URI to the output color profile
+     */
     public void setOutputProfileURI(String outputProfileURI) {
         this.outputProfileURI = outputProfileURI;
     }
 
+    /**
+     * Sets the filter map to be used by the PDF renderer.
+     * @param filterMap the filter map
+     */
     public void setFilterMap(Map filterMap) {
         this.filterMap = filterMap;
     }
index cbc0a8ec9d17fa70b59800986fb180fa98f8381d..cb7c7cf89a78bd7d64c9d6908b1500660a627947 100644 (file)
@@ -36,6 +36,8 @@ import org.apache.batik.util.SVGConstants;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.util.QName;
+
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.fonts.FontInfo;
@@ -51,8 +53,8 @@ import org.apache.fop.render.RendererContextConstants;
 import org.apache.fop.svg.PDFAElementBridge;
 import org.apache.fop.svg.PDFBridgeContext;
 import org.apache.fop.svg.PDFGraphics2D;
+import org.apache.fop.svg.SVGEventProducer;
 import org.apache.fop.svg.SVGUserAgent;
-import org.apache.fop.util.QName;
 
 /**
  * PDF XML handler for SVG (uses Apache Batik).
@@ -144,8 +146,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler
             try {
                 super.renderSVGDocument(context, doc);
             } catch (IOException ioe) {
-                log.error("I/O error while rendering SVG graphic: "
-                                       + ioe.getMessage(), ioe);
+                SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                        context.getUserAgent().getEventBroadcaster());
+                eventProducer.svgRenderingError(this, ioe, getDocumentURI(doc));
             }
             return;
         }
@@ -153,15 +156,13 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler
         int yOffset = pdfInfo.currentYPosition;
 
         FOUserAgent userAgent = context.getUserAgent(); 
-        log.debug("Generating SVG at " 
-                + userAgent.getTargetResolution()
-                + "dpi.");
         final float deviceResolution = userAgent.getTargetResolution();
-        log.debug("Generating SVG at " + deviceResolution + "dpi.");
-        log.debug("Generating SVG at " + deviceResolution + "dpi.");
+        if (log.isDebugEnabled()) {
+            log.debug("Generating SVG at " + deviceResolution + "dpi.");
+        }
         
         final float uaResolution = userAgent.getSourceResolution();
-        SVGUserAgent ua = new SVGUserAgent(25.4f / uaResolution, new AffineTransform());
+        SVGUserAgent ua = new SVGUserAgent(userAgent, new AffineTransform());
 
         //Scale for higher resolution on-the-fly images from Batik
         double s = uaResolution / deviceResolution;
@@ -188,8 +189,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler
             root = builder.build(ctx, doc);
             builder = null;
         } catch (Exception e) {
-            log.error("svg graphic could not be built: "
-                                   + e.getMessage(), e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgNotBuilt(this, e, getDocumentURI(doc));
             return;
         }
         // get the 'width' and 'height' attributes of the SVG document
@@ -261,8 +263,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler
             root.paint(graphics);
             pdfInfo.currentStream.add(graphics.getString());
         } catch (Exception e) {
-            log.error("svg graphic could not be rendered: "
-                                   + e.getMessage(), e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgRenderingError(this, e, getDocumentURI(doc));
         }
         pdfInfo.pdfState.pop();
         renderer.restoreGraphicsState();
diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.java b/src/java/org/apache/fop/render/ps/PSEventProducer.java
new file mode 100644 (file)
index 0000000..451ed1c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Event producer interface for events generated by the PostScript renderer.
+ */
+public interface PSEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static PSEventProducer get(EventBroadcaster broadcaster) {
+            return (PSEventProducer)broadcaster.getEventProducerFor(
+                    PSEventProducer.class);
+        }
+    }
+
+    /** Event model factory for this event producer. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * A PostScript dictionary could not be parsed.
+     * @param source the event source
+     * @param content the PostScript content
+     * @param e the original exception
+     * @event.severity ERROR
+     */
+    void postscriptDictionaryParseError(Object source, String content, Exception e);
+    
+}
diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.xml b/src/java/org/apache/fop/render/ps/PSEventProducer.xml
new file mode 100644 (file)
index 0000000..a007822
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="org.apache.fop.render.ps.PSEventProducer.postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message>
+</catalogue>
index 7e32977e6ad1fe34f5668800e0f4c8693f27e562..e4d582ba2a9eafd955534274faf0b7d80fc5f530 100644 (file)
@@ -85,6 +85,7 @@ import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fonts.Font;
@@ -95,6 +96,7 @@ import org.apache.fop.render.AbstractPathOrientedRenderer;
 import org.apache.fop.render.Graphics2DAdapter;
 import org.apache.fop.render.ImageAdapter;
 import org.apache.fop.render.RendererContext;
+import org.apache.fop.render.RendererEventProducer;
 import org.apache.fop.render.ps.extensions.PSCommentAfter;
 import org.apache.fop.render.ps.extensions.PSCommentBefore;
 import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
@@ -296,7 +298,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer
      */
     protected void handleIOTrouble(IOException ioe) {
         if (!ioTrouble) {
-            log.error("Error while writing to target file", ioe);
+            RendererEventProducer eventProducer = RendererEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.ioError(this, ioe);
             ioTrouble = true;
         }
     }
@@ -522,12 +526,17 @@ public class PSRenderer extends AbstractPathOrientedRenderer
             }
 
         } catch (ImageException ie) {
-            log.error("Error while processing image: "
-                    + (info != null ? info.toString() : uri), ie);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
         } catch (FileNotFoundException fe) {
-            log.error(fe.getMessage());
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
         } catch (IOException ioe) {
-            handleIOTrouble(ioe);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
         }
     }
 
@@ -1070,8 +1079,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer
                             try {
                                 this.pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
                             } catch (PSDictionaryFormatException e) {
-                                log.error("Failed to parse dictionary string: "
-                                        + e.getMessage() + ", content = '" + content + "'");
+                                PSEventProducer eventProducer = PSEventProducer.Provider.get(
+                                        getUserAgent().getEventBroadcaster());
+                                eventProducer.postscriptDictionaryParseError(this, content, e);
                             }
                         }
                     } else if (attachment instanceof PSCommentBefore) {
@@ -1169,8 +1179,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer
                         try {
                             pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
                         } catch (PSDictionaryFormatException e) {
-                            log.error("failed to parse dictionary string: "
-                                    + e.getMessage() + ", [" + content + "]");
+                            PSEventProducer eventProducer = PSEventProducer.Provider.get(
+                                    getUserAgent().getEventBroadcaster());
+                            eventProducer.postscriptDictionaryParseError(this, content, e);
                         }
                     }
                 }
index 5cfe617c8f43f3f00a4a877ab5dbcfa67512e985..ebe098282d12a768c65f9d3090283d3b6612973e 100644 (file)
@@ -23,31 +23,24 @@ package org.apache.fop.render.ps;
 import java.awt.geom.AffineTransform;
 import java.io.IOException;
 
-// DOM
 import org.w3c.dom.Document;
-import org.w3c.dom.svg.SVGDocument;
-import org.w3c.dom.svg.SVGSVGElement;
 
-// Batik
 import org.apache.avalon.framework.configuration.Configuration;
-import org.apache.batik.bridge.GVTBuilder;
 import org.apache.batik.bridge.BridgeContext;
-import org.apache.batik.bridge.ViewBox;
-import org.apache.batik.dom.svg.SVGDOMImplementation;
+import org.apache.batik.bridge.GVTBuilder;
 import org.apache.batik.gvt.GraphicsNode;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
+import org.apache.xmlgraphics.ps.PSGenerator;
 
-// FOP
 import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.render.AbstractGenericSVGHandler;
 import org.apache.fop.render.Renderer;
-import org.apache.fop.render.XMLHandler;
 import org.apache.fop.render.RendererContext;
+import org.apache.fop.svg.SVGEventProducer;
 import org.apache.fop.svg.SVGUserAgent;
-import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
-import org.apache.xmlgraphics.ps.PSGenerator;
-
-// Commons-Logging
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 
 /**
  * PostScript XML handler for SVG. Uses Apache Batik for SVG processing.
@@ -57,7 +50,8 @@ import org.apache.commons.logging.LogFactory;
  *
  * @version $Id$
  */
-public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
+public class PSSVGHandler extends AbstractGenericSVGHandler
+            implements PSRendererContextConstants {
 
     /** logging instance */
     private static Log log = LogFactory.getLog(PSSVGHandler.class);
@@ -68,16 +62,6 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
     public PSSVGHandler() {
     }
 
-    /** {@inheritDoc} */
-    public void handleXML(RendererContext context, 
-                Document doc, String ns) throws Exception {
-        PSInfo psi = getPSInfo(context);
-
-        if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) {
-            renderSVGDocument(context, doc, psi);
-        }
-    }
-
     /**
      * Get the pdf information from the render context.
      *
@@ -234,10 +218,10 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
      * Render the svg document.
      * @param context the renderer context
      * @param doc the svg document
-     * @param psInfo the pdf information of the current context
      */
     protected void renderSVGDocument(RendererContext context,
-            Document doc, PSInfo psInfo) {
+            Document doc) {
+        PSInfo psInfo = getPSInfo(context);
         int xOffset = psInfo.currentXPosition;
         int yOffset = psInfo.currentYPosition;
         PSGenerator gen = psInfo.psGenerator;
@@ -250,9 +234,7 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
         }
 
         SVGUserAgent ua
-             = new SVGUserAgent(
-                context.getUserAgent().getSourcePixelUnitToMillimeter(),
-                new AffineTransform());
+             = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
 
         PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
         graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
@@ -273,8 +255,9 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
         try {
             root = builder.build(ctx, doc);
         } catch (Exception e) {
-            log.error("SVG graphic could not be built: "
-                                   + e.getMessage(), e);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgNotBuilt(this, e, getDocumentURI(doc));
             return;
         }
         // get the 'width' and 'height' attributes of the SVG document
@@ -305,10 +288,10 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
             // viewBox puts it.
             gen.concatMatrix(sx, 0, 0, sy, xOffset / 1000f, yOffset / 1000f);
 
+            /*
             SVGSVGElement svg = ((SVGDocument)doc).getRootElement();
             AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg,
-                    psInfo.getWidth() / 1000f, psInfo.getHeight() / 1000f);
-            /*
+                    psInfo.getWidth() / 1000f, psInfo.getHeight() / 1000f, ctx);
             if (!at.isIdentity()) {
                 double[] vals = new double[6];
                 at.getMatrix(vals);
@@ -322,15 +305,17 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
             try {
                 root.paint(graphics);
             } catch (Exception e) {
-                log.error("SVG graphic could not be rendered: "
-                                       + e.getMessage(), e);
+                SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                        context.getUserAgent().getEventBroadcaster());
+                eventProducer.svgRenderingError(this, e, getDocumentURI(doc));
             }
 
             gen.restoreGraphicsState();
             gen.commentln("%FOPEndSVG");
         } catch (IOException ioe) {
-            log.error("SVG graphic could not be rendered: "
-                                   + ioe.getMessage(), ioe);
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgRenderingError(this, ioe, getDocumentURI(doc));
         }
     }
 
@@ -339,10 +324,5 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants {
         return (renderer instanceof PSRenderer);
     }
     
-    /** {@inheritDoc} */
-    public String getNamespace() {
-        return SVGDOMImplementation.SVG_NAMESPACE_URI;
-    }
-
 }
 
index 0dfb8029faafd72db578d480e8d3cda06b07e0ec..1a363c90e8e0a174d9aaa2855abf5350aee80d3e 100644 (file)
@@ -20,8 +20,8 @@
 package org.apache.fop.render.ps;
 
 import java.awt.geom.Dimension2D;
-import java.awt.image.RenderedImage;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.RenderedImage;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -68,6 +68,7 @@ import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment;
 import org.apache.xmlgraphics.ps.dsc.tools.DSCTools;
 
 import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fonts.FontInfo;
 
 /**
@@ -321,7 +322,10 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
                     throw new UnsupportedOperationException("Unsupported image type: " + img);
                 }
             } catch (ImageException ie) {
-                throw new IOException("Error while generating form for image: " + ie.getMessage());
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        userAgent.getEventBroadcaster());
+                eventProducer.imageError(resTracker, (info != null ? info.toString() : uri),
+                        ie, null);
             }
         }
     }
index a6e77fb134791e87d8e4585e10a65305715cc59c..1eb1d9d13a5af9b26d9f7c5cd7a2882c8f5f750a 100644 (file)
@@ -22,7 +22,6 @@ package org.apache.fop.render.ps.extensions;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.ValidationException;
 
 /**
  * Base postscript commment element class
@@ -46,8 +45,8 @@ public abstract class AbstractPSCommentElement extends AbstractPSExtensionElemen
     protected void startOfNode() throws FOPException {
         if (parent.getNameId() != Constants.FO_DECLARATIONS
                 && parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) {
-          throw new ValidationException(getName()
-          + " must be a child of fo:declarations or fo:simple-page-master.");            
+            invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(),
+                    "rule.childOfSPMorDeclarations");
         }
     }
 
index a10bb75184138a2b3ef1edc53d1a4de398a43e9d..31e44d2d279838779325fd02d482176d36a050fa 100644 (file)
 package org.apache.fop.render.ps.extensions;
 
 // FOP
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
 
-import org.xml.sax.Locator;
-
 /**
  * Base class for the PostScript-specific extension elements.
  */
@@ -98,7 +98,7 @@ public abstract class AbstractPSExtensionElement extends FONode {
      * @see org.apache.fop.fo.FONode#getNormalNamespacePrefix()
      */
     public String getNormalNamespacePrefix() {
-        return "fox";
+        return "ps";
     }
 
     /**
index 6823d75d9c173ff71bdb706192172f764b85e7d8..78b2f91eb586522ac567f983fa6748d2d6b37d9a 100644 (file)
 package org.apache.fop.render.ps.extensions;
 
 // FOP
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
 
 /**
  * Base class for the PostScript-specific extension elements.
@@ -36,15 +37,15 @@ public abstract class AbstractPSExtensionObject extends FONode {
     private PSSetupCode setupCode = new PSSetupCode();
     
     /**
+     * Main constructor.
+     * @param parent the parent node
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public AbstractPSExtensionObject(FONode parent) {
         super(parent);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
                 throws ValidationException {
         if (FO_URI.equals(nsURI)) {
@@ -71,7 +72,7 @@ public abstract class AbstractPSExtensionObject extends FONode {
     
     /**{@inheritDoc} */
     public String getNormalNamespacePrefix() {
-        return "fox";
+        return "ps";
     }
 
     /** {@inheritDoc} */
index 306cd7bda340b151ac26f15451fdc91906531a48..1dd0f4be66693438f7c5db9080d0b7c278c34fb4 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.fop.fo.extensions.ExtensionAttachment;
  */
 public class PSCommentAfterElement extends AbstractPSCommentElement {
 
+    /** the element name */
     protected static final String ELEMENT = "ps-comment-after";
 
     /**
index 6058f355bf59ec0400a5330ae1453cc82c2fb1ae..5d3c863f5b7f76766d93bed6ef92fcac7cbf219d 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.fop.fo.extensions.ExtensionAttachment;
  */
 public class PSCommentBeforeElement extends AbstractPSCommentElement {
 
+    /** the element name */
     protected static final String ELEMENT = "ps-comment-before";
 
     /**
index 456d97430e9200324c81a6b0cdff7f1074e178c4..e695007361bc3d22666e352fcc11815a96d3563b 100644 (file)
 
 package org.apache.fop.render.ps.extensions;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.apache.fop.util.ContentHandlerFactory;
 import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
 /**
  * ContentHandler (parser) for restoring PSExtension objects from XML.
@@ -91,25 +93,19 @@ public class PSExtensionHandler extends DefaultHandler
         content.append(ch, start, length);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void endDocument() throws SAXException {
         if (listener != null) {
             listener.notifyObjectBuilt(getObject());
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public Object getObject() {
         return returnedObject;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     public void setObjectBuiltListener(ObjectBuiltListener listener) {
         this.listener = listener;
     }
index ad46b936420a599852454ea7edd396c35e772f09..207c11e76fec18c6e6eddc751892095f4bc53a96 100644 (file)
@@ -22,13 +22,13 @@ package org.apache.fop.render.ps.extensions;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.ValidationException;
 
 /**
  * Extension element for fox:ps-page-setup-code. 
  */
 public class PSPageSetupCodeElement extends AbstractPSExtensionObject {
 
+    /** The element name */
     protected static final String ELEMENT = "ps-page-setup-code";
     
     /**
@@ -43,7 +43,8 @@ public class PSPageSetupCodeElement extends AbstractPSExtensionObject {
     protected void startOfNode() throws FOPException {
         super.startOfNode();
         if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) {
-            throw new ValidationException(getName() + " must be a child of fo:simple-page-master.");
+            invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(),
+                "rule.childOfSPM");
         }
     }
     
index b512c688884bdb2d9a8ad6851dd861a09299e41f..21acc8001b5563e9e0869b7e25fc89fcf8f63825 100644 (file)
 
 package org.apache.fop.render.ps.extensions;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
-import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
-import org.xml.sax.Attributes;
-import org.xml.sax.Locator;
 
 /**
  * Extension element for ps:ps-setpagedevice. 
  */
 public class PSSetPageDeviceElement extends AbstractPSExtensionElement {
 
+    /** The element name */
     protected static final String ELEMENT = "ps-setpagedevice";
 
     /**
@@ -52,8 +53,8 @@ public class PSSetPageDeviceElement extends AbstractPSExtensionElement {
         super.startOfNode();
         if ( !((parent.getNameId() == Constants.FO_DECLARATIONS)
                 || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) {
-            throw new ValidationException( getName()
-                    + " must be a child of fo:declarations or fo:simple-page-master.");
+            invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(),
+                    "rule.childOfSPMorDeclarations");
         }
     }
 
index ec7216c4462256a5b25505fab65bf202e9ae9964..e76dfeb6438e33ebbe4f4e709de2281b34d30de8 100644 (file)
@@ -22,13 +22,13 @@ package org.apache.fop.render.ps.extensions;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.ValidationException;
 
 /**
  * Extension element for fox:ps-setup-code. 
  */
 public class PSSetupCodeElement extends AbstractPSExtensionObject {
 
+    /** The element name */
     protected static final String ELEMENT = "ps-setup-code";
     
     /**
@@ -43,7 +43,8 @@ public class PSSetupCodeElement extends AbstractPSExtensionObject {
     protected void startOfNode() throws FOPException {
         super.startOfNode();
         if (parent.getNameId() != Constants.FO_DECLARATIONS) {
-            throw new ValidationException(getName() + " must be a child of fo:declarations.");
+            invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(),
+                    "rule.childOfDeclarations");
         }
     }
     
diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.java b/src/java/org/apache/fop/render/rtf/RTFEventProducer.java
new file mode 100644 (file)
index 0000000..a2646af
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.rtf;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+import org.apache.fop.fo.FONode;
+
+/**
+ * Event producer interface for events generated by the RTF renderer.
+ */
+public interface RTFEventProducer extends EventProducer {
+
+    /** Provider class for the event producer. */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static RTFEventProducer get(EventBroadcaster broadcaster) {
+            return (RTFEventProducer)broadcaster.getEventProducerFor(
+                    RTFEventProducer.class);
+        }
+    }
+
+    /** Event model factory for this event producer. */
+    public static class EventModelFactory extends AbstractEventModelFactory {
+
+        /** {@inheritDoc} */
+        public EventModel createEventModel() {
+            return loadModel(getClass(), "event-model.xml");
+        }
+        
+    }
+    
+    /**
+     * The RTF handler only supports simple-page-masters.
+     * @param source the event source
+     * @param masterReference the reference page-master-set
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void onlySPMSupported(Object source, String masterReference, Locator loc);
+    
+    /**
+     * No simple-page-master could be determined-
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void noSPMFound(Object source, Locator loc);
+    
+    /**
+     * The RTF handler requires explicit table-columns for now.
+     * @param source the event source
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void explicitTableColumnsRequired(Object source, Locator loc);
+    
+    /**
+     * The RTF handler ignored some deferred event (i.e. an unsupported element).
+     * @param source the event source
+     * @param node the FO tree node being ignored
+     * @param start true for start, false for end
+     * @param loc the location of the error or null
+     * @event.severity WARN
+     */
+    void ignoredDeferredEvent(Object source, FONode node, boolean start, Locator loc);
+    
+}
diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml
new file mode 100644 (file)
index 0000000..8f1f21a
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.onlySPMSupported">Only simple-page-masters are supported on page-sequences. Using default simple-page-master from page-sequence-master "{masterReference}".{{locator}}</message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.noSPMFound">No simple-page-master could be determined.</message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.explicitTableColumnsRequired">No table-columns found on table. RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}}</message>
+  <message key="org.apache.fop.render.rtf.RTFEventProducer.ignoredDeferredEvent">Ignored deferred event for {node} ({start,if,start,end}).{{locator}}</message>
+</catalogue>
index d2f2c4192389ab2fb4e45f86c89a3928fa829391..88e34e17abb0c1ef378ed889eb918e6077859681 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.render.rtf;
 
 // Java
 import java.awt.geom.Point2D;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -51,6 +52,7 @@ import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.datatypes.LengthBase;
 import org.apache.fop.datatypes.SimplePercentBaseContext;
+import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.fo.FONode;
@@ -88,6 +90,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
 import org.apache.fop.fo.properties.FixedLength;
 import org.apache.fop.fonts.FontSetup;
 import org.apache.fop.render.DefaultFontResolver;
+import org.apache.fop.render.RendererEventProducer;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfAfterContainer;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfBeforeContainer;
 import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfListContainer;
@@ -158,6 +161,16 @@ public class RTFHandler extends FOEventHandler {
         FontSetup.setup(fontInfo, null, new DefaultFontResolver(userAgent));
     }
 
+    /**
+     * Central exception handler for I/O exceptions.
+     * @param ioe IOException to handle
+     */
+    protected void handleIOTrouble(IOException ioe) {
+        RendererEventProducer eventProducer = RendererEventProducer.Provider.get(
+                getUserAgent().getEventBroadcaster());
+        eventProducer.ioError(this, ioe);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -195,9 +208,9 @@ public class RTFHandler extends FOEventHandler {
                 this.pagemaster
                         = pageSeq.getRoot().getLayoutMasterSet().getSimplePageMaster(reference);
                 if (this.pagemaster == null) {
-                    log.warn("Only simple-page-masters are supported on page-sequences: " 
-                            + reference);
-                    log.warn("Using default simple-page-master from page-sequence-master...");
+                    RTFEventProducer eventProducer = RTFEventProducer.Provider.get(
+                            getUserAgent().getEventBroadcaster());
+                    eventProducer.onlySPMSupported(this, reference, pageSeq.getLocator());
                     PageSequenceMaster master 
                         = pageSeq.getRoot().getLayoutMasterSet().getPageSequenceMaster(reference);
                     this.pagemaster = master.getNextSimplePageMaster(
@@ -218,7 +231,9 @@ public class RTFHandler extends FOEventHandler {
                     PageAttributesConverter.convertPageAttributes(
                             pagemaster));
             } else {
-                log.warn("No simple-page-master could be determined!");
+                RTFEventProducer eventProducer = RTFEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.noSPMFound(this, pageSeq.getLocator());
             }
 
             builderContext.pushContainer(sect);
@@ -226,9 +241,7 @@ public class RTFHandler extends FOEventHandler {
             bHeaderSpecified = false;
             bFooterSpecified = false;
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("startPageSequence: " + ioe.getMessage(), ioe);
-            //TODO throw new FOPException(ioe);
+            handleIOTrouble(ioe);
         } catch (FOPException fope) {
             // TODO could we throw Exception in all FOEventHandler events?
             log.error("startPageSequence: " + fope.getMessage(), fope);
@@ -338,8 +351,7 @@ public class RTFHandler extends FOEventHandler {
                 log.warn("A " + fl.getLocalName() + " has been skipped: " + fl.getFlowName());
             }
         } catch (IOException ioe) {
-            log.error("startFlow: " + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startFlow: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -396,9 +408,7 @@ public class RTFHandler extends FOEventHandler {
             textrun.pushBlockAttributes(rtfAttr);
             textrun.addBookmark(bl.getId());
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("startBlock: " + ioe.getMessage());
-            throw new RuntimeException("IOException: " + ioe);
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startBlock: " + e.getMessage());
             throw new RuntimeException("Exception: " + e);
@@ -427,8 +437,7 @@ public class RTFHandler extends FOEventHandler {
             textrun.popBlockAttributes();
 
         } catch (IOException ioe) {
-            log.error("startBlock:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startBlock:" + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -457,9 +466,7 @@ public class RTFHandler extends FOEventHandler {
             textrun.addParagraphBreak();
             textrun.pushBlockAttributes(rtfAttr);
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("startBlock: " + ioe.getMessage());
-            throw new RuntimeException("IOException: " + ioe);
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startBlock: " + e.getMessage());
             throw new RuntimeException("Exception: " + e);
@@ -486,8 +493,7 @@ public class RTFHandler extends FOEventHandler {
             textrun.popBlockAttributes();
 
         } catch (IOException ioe) {
-            log.error("startBlock:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startBlock:" + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -530,6 +536,8 @@ public class RTFHandler extends FOEventHandler {
             table.setBorderAttributes(borderAttributes);
             
             builderContext.pushContainer(table);
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startTable:" + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -590,7 +598,6 @@ public class RTFHandler extends FOEventHandler {
             log.error("startColumn: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
         }
-
     }
 
      /**
@@ -649,8 +656,7 @@ public class RTFHandler extends FOEventHandler {
             textrun.pushInlineAttributes(rtfAttr);
             textrun.addBookmark(inl.getId());
         } catch (IOException ioe) {
-            log.error("startInline:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (FOPException fe) {
             log.error("startInline:" + fe.getMessage());
             throw new RuntimeException(fe.getMessage());
@@ -677,8 +683,7 @@ public class RTFHandler extends FOEventHandler {
             RtfTextrun textrun = container.getTextrun();
             textrun.popInlineAttributes();
         } catch (IOException ioe) {
-            log.error("startInline:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startInline:" + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -698,6 +703,8 @@ public class RTFHandler extends FOEventHandler {
 
             RtfTable tbl = (RtfTable)builderContext.getContainer(RtfTable.class, true, this);
             tbl.setHeaderAttribs(atts);
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startBody: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -715,6 +722,8 @@ public class RTFHandler extends FOEventHandler {
         try {
             RtfTable tbl = (RtfTable)builderContext.getContainer(RtfTable.class, true, this);
             tbl.setHeaderAttribs(null);
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("endBody: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -745,6 +754,8 @@ public class RTFHandler extends FOEventHandler {
 
             // reset column iteration index to correctly access column widths
             builderContext.getTableContext().selectFirstColumn();
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startRow: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -778,6 +789,8 @@ public class RTFHandler extends FOEventHandler {
                 
                 tctx.selectNextColumn();
             }
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("endRow: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -870,6 +883,8 @@ public class RTFHandler extends FOEventHandler {
             }
             
             builderContext.pushContainer(cell);
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startCell: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -906,8 +921,7 @@ public class RTFHandler extends FOEventHandler {
                 ListAttributesConverter.convertAttributes(lb));
             builderContext.pushContainer(newList);
         } catch (IOException ioe) {
-            log.error("startList: " + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (FOPException fe) {
             log.error("startList: " + fe.getMessage());
             throw new RuntimeException(fe.getMessage());
@@ -961,8 +975,7 @@ public class RTFHandler extends FOEventHandler {
             
             builderContext.pushContainer(list.newListItem());
         } catch (IOException ioe) {
-            log.error("startList: " + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startList: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -995,8 +1008,7 @@ public class RTFHandler extends FOEventHandler {
             RtfListItemLabel label = item.new RtfListItemLabel(item);
             builderContext.pushContainer(label);
         } catch (IOException ioe) {
-            log.error("startPageNumber:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startPageNumber: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -1077,8 +1089,7 @@ public class RTFHandler extends FOEventHandler {
             builderContext.pushContainer(link);
 
         } catch (IOException ioe) {
-            log.error("startLink:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startLink: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -1104,21 +1115,28 @@ public class RTFHandler extends FOEventHandler {
             return;
         }
 
+        String uri = eg.getURL();
+        ImageInfo info = null;
         try {
-            String uri = eg.getURL();
 
             //set image data
             FOUserAgent userAgent = eg.getUserAgent();
             ImageManager manager = userAgent.getFactory().getImageManager();
-            ImageInfo info = manager.getImageInfo(uri, userAgent.getImageSessionContext());
-            if (info == null) {
-                log.error("Image could not be found: " + uri);
-                return;
-            }
+            info = manager.getImageInfo(uri, userAgent.getImageSessionContext());
             
             putGraphic(eg, info);
-        } catch (Exception e) {
-            log.error("Error while handling an external-graphic: " + e.getMessage(), e);
+        } catch (ImageException ie) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
+        } catch (FileNotFoundException fe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
+        } catch (IOException ioe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
         }
     }
 
@@ -1144,6 +1162,12 @@ public class RTFHandler extends FOEventHandler {
             // Set the image size to the size of the svg.
             Point2D csize = new Point2D.Float(-1, -1);
             Point2D intrinsicDimensions = child.getDimension(csize);
+            if (intrinsicDimensions == null) {
+                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.ifoNoIntrinsicSize(this, child.getLocator());
+                return;
+            }
             size.setSizeInMillipoints(
                     (int)Math.round(intrinsicDimensions.getX() * 1000),
                     (int)Math.round(intrinsicDimensions.getY() * 1000));
@@ -1157,8 +1181,14 @@ public class RTFHandler extends FOEventHandler {
             Image converted = manager.convertImage(image, FLAVORS);
             putGraphic(ifo, converted);
             
-        } catch (Exception e) {
-            log.error("Error while handling an instream-foreign-object: " + e.getMessage(), e);
+        } catch (ImageException ie) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, null, ie, null);
+        } catch (IOException ioe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, null, ioe, null);
         }
     }
 
@@ -1183,7 +1213,9 @@ public class RTFHandler extends FOEventHandler {
 
             putGraphic(abstractGraphic, image);
         } catch (ImageException ie) {
-            log.error("Error while loading/processing image: " + info.getOriginalURI(), ie);
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, null, ie, null);
         }
     }
     
@@ -1210,8 +1242,9 @@ public class RTFHandler extends FOEventHandler {
         }
 
         if (rawData == null) {
-            log.warn(FONode.decorateWithContextInfo("Image could not be embedded: "
-                    + image, abstractGraphic));
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageWritingError(this, null);
             return;
         }
 
@@ -1322,9 +1355,7 @@ public class RTFHandler extends FOEventHandler {
             builderContext.pushContainer(rtfFootnote);
 
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("startFootnote: " + ioe.getMessage());
-            throw new RuntimeException("IOException: " + ioe);
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startFootnote: " + e.getMessage());
             throw new RuntimeException("Exception: " + e);
@@ -1358,9 +1389,7 @@ public class RTFHandler extends FOEventHandler {
 
             rtfFootnote.startBody();
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("startFootnoteBody: " + ioe.getMessage());
-            throw new RuntimeException("IOException: " + ioe);
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startFootnoteBody: " + e.getMessage());
             throw new RuntimeException("Exception: " + e);
@@ -1383,9 +1412,7 @@ public class RTFHandler extends FOEventHandler {
 
             rtfFootnote.endBody();
         } catch (IOException ioe) {
-            // TODO could we throw Exception in all FOEventHandler events?
-            log.error("endFootnoteBody: " + ioe.getMessage());
-            throw new RuntimeException("IOException: " + ioe);
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("endFootnoteBody: " + e.getMessage());
             throw new RuntimeException("Exception: " + e);
@@ -1421,10 +1448,8 @@ public class RTFHandler extends FOEventHandler {
             textrun.pushInlineAttributes(rtfAttr);
             textrun.addString(new String(data, start, length - start));
             textrun.popInlineAttributes();
-         } catch (IOException ioe) {
-            // FIXME could we throw Exception in all FOEventHandler events?
-            log.error("characters: " + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("characters:" + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -1452,8 +1477,7 @@ public class RTFHandler extends FOEventHandler {
             RtfTextrun textrun = container.getTextrun();
             textrun.addPageNumber(rtfAttr);
         } catch (IOException ioe) {
-            log.error("startPageNumber:" + ioe.getMessage());
-            throw new RuntimeException(ioe.getMessage());
+            handleIOTrouble(ioe);
         } catch (Exception e) {
             log.error("startPageNumber: " + e.getMessage());
             throw new RuntimeException(e.getMessage());
@@ -1611,7 +1635,9 @@ public class RTFHandler extends FOEventHandler {
                 endCell( (TableCell) foNode);
             }
         } else {
-            log.warn("Ignored deferred event for " + foNode);
+            RTFEventProducer eventProducer = RTFEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.ignoredDeferredEvent(this, foNode, bStart, foNode.getLocator());
         }
     }
 
@@ -1655,8 +1681,9 @@ public class RTFHandler extends FOEventHandler {
                 }
             } else {
                 //TODO Implement implicit column setup handling!
-                log.warn("No table-columns found on table. RTF output requires that all"
-                        + " table-columns for a table are defined. Output will be incorrect.");
+                RTFEventProducer eventProducer = RTFEventProducer.Provider.get(
+                        getUserAgent().getEventBroadcaster());
+                eventProducer.explicitTableColumnsRequired(this, table.getLocator());
             }
 
             //recurse table-header
index 66cce0ae1815d532b0e00eef29c5cfdbfc039103..eb3c92e1ba1998c9fa2308528409b02402c12b54 100644 (file)
@@ -41,6 +41,7 @@ import org.xml.sax.SAXException;
 import org.xml.sax.ext.LexicalHandler;
 import org.xml.sax.helpers.AttributesImpl;
 
+import org.apache.xmlgraphics.util.QName;
 import org.apache.xmlgraphics.util.XMLizable;
 
 import org.apache.fop.apps.FOPException;
@@ -90,7 +91,6 @@ import org.apache.fop.render.Renderer;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.XMLHandler;
 import org.apache.fop.util.ColorUtil;
-import org.apache.fop.util.QName;
 
 /**
  * Renderer that renders areas to XML for debugging purposes.
diff --git a/src/java/org/apache/fop/svg/SVGEventProducer.java b/src/java/org/apache/fop/svg/SVGEventProducer.java
new file mode 100644 (file)
index 0000000..8894f3f
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.svg;
+
+import org.apache.fop.events.EventBroadcaster;
+import org.apache.fop.events.EventProducer;
+
+/**
+ * Event producer interface for SVG-related events.
+ */
+public interface SVGEventProducer extends EventProducer {
+
+    /**
+     * Provider class for the event producer.
+     */
+    class Provider {
+        
+        /**
+         * Returns an event producer.
+         * @param broadcaster the event broadcaster to use
+         * @return the event producer
+         */
+        public static SVGEventProducer get(EventBroadcaster broadcaster) {
+            return (SVGEventProducer)broadcaster.getEventProducerFor(
+                    SVGEventProducer.class);
+        }
+    }
+
+    /**
+     * Error during SVG processing. Either message or e must be set.
+     * @param source the event source
+     * @param message the error message (or null)
+     * @param e the exception (or null)
+     * @event.severity ERROR
+     */
+    void error(Object source, String message, Exception e);
+    
+    /**
+     * Alert during SVG processing.
+     * @param source the event source
+     * @param message the error message
+     * @event.severity WARN
+     */
+    void alert(Object source, String message);
+    
+    /**
+     * Info during SVG processing.
+     * @param source the event source
+     * @param message the error message
+     * @event.severity INFO
+     */
+    void info(Object source, String message);
+    
+    /**
+     * SVG graphic could not be built due to an exception.
+     * @param source the event source
+     * @param e the original exception
+     * @param uri the URI of the SVG graphic
+     * @event.severity ERROR
+     */
+    void svgNotBuilt(Object source, Exception e, String uri);
+    
+    /**
+     * SVG graphic could not be rendered due to an exception.
+     * @param source the event source
+     * @param e the original exception
+     * @param uri the URI of the SVG graphic
+     * @event.severity ERROR
+     */
+    void svgRenderingError(Object source, Exception e, String uri);
+    
+}
index 540f490dedb2ecdc829705600bdbb33c5586d373..8d7754fcb1f6d67b845be6e713b5070823c1a9a5 100644 (file)
  
 package org.apache.fop.svg;
 
-import javax.xml.parsers.SAXParserFactory;
-import org.apache.batik.bridge.UserAgentAdapter;
-
-// Java
 import java.awt.geom.AffineTransform;
-import java.awt.geom.Dimension2D;
-import java.awt.Dimension;
 
-// Commons-Logging
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.apache.fop.apps.FOUserAgent;
 
 /**
- * The SVG user agent.
- * This is an implementation of the batik svg user agent
- * for handling errors and getting user agent values.
+ * The SVG user agent. This is an implementation of the Batik SVG user agent.
  */
-public class SVGUserAgent extends UserAgentAdapter {
-    private AffineTransform currentTransform = null;
-    private float pixelUnitToMillimeter = 0.0f;
+public class SVGUserAgent extends SimpleSVGUserAgent {
+    
+    private SVGEventProducer eventProducer;
+    private Exception lastException;
 
     /**
-     * logging instance
+     * Creates a new SVGUserAgent.
+     * @param foUserAgent the FO user agent to associate with this SVG user agent
+     * @param at the current transform
      */
-    private Log logger = LogFactory.getLog(SVGUserAgent.class);
+    public SVGUserAgent(FOUserAgent foUserAgent, AffineTransform at) {
+        super(foUserAgent.getSourcePixelUnitToMillimeter(), at);
+        this.eventProducer = SVGEventProducer.Provider.get(foUserAgent.getEventBroadcaster());
+    }
 
     /**
      * Creates a new SVGUserAgent.
-     * @param pixelUnitToMM the pixel to millimeter conversion factor
-     * currently in effect
-     * @param at the current transform
+     * @param foUserAgent the FO user agent to associate with this SVG user agent
      */
-    public SVGUserAgent(float pixelUnitToMM, AffineTransform at) {
-        pixelUnitToMillimeter = pixelUnitToMM;
-        currentTransform = at;
+    public SVGUserAgent(FOUserAgent foUserAgent) {
+        this(foUserAgent, new AffineTransform());
     }
-
+    
     /**
-     * Returns the logger associated with this user agent.
-     * @return Logger the logger
+     * Returns the last exception sent to the {@link #displayError(Exception)} method.
+     * @return the last exception or null if no exception occurred
      */
-    protected final Log getLogger() {
-        return logger;
+    public Exception getLastException() {
+        return this.lastException;
     }
 
     /**
@@ -69,7 +62,7 @@ public class SVGUserAgent extends UserAgentAdapter {
      * @param message the message to display
      */
     public void displayError(String message) {
-        logger.error(message);
+        this.eventProducer.error(this, message, null);
     }
 
     /**
@@ -77,7 +70,8 @@ public class SVGUserAgent extends UserAgentAdapter {
      * @param ex the exception to display
      */
     public void displayError(Exception ex) {
-        logger.error("SVG Error" + ex.getMessage(), ex);
+        this.lastException = ex;
+        this.eventProducer.error(this, ex.getLocalizedMessage(), ex);
     }
 
     /**
@@ -86,7 +80,7 @@ public class SVGUserAgent extends UserAgentAdapter {
      * @param message the message to display
      */
     public void displayMessage(String message) {
-        logger.info(message);
+        this.eventProducer.info(this, message);
     }
 
     /**
@@ -94,78 +88,7 @@ public class SVGUserAgent extends UserAgentAdapter {
      * @param message the message to display
      */
     public void showAlert(String message) {
-        logger.warn(message);
-    }
-
-    /**
-     * Returns a customized the pixel to mm factor.
-     * @return the pixel unit to millimeter conversion factor
-     */
-    public float getPixelUnitToMillimeter() {
-        return pixelUnitToMillimeter;
+        this.eventProducer.alert(this, message);
     }
 
-    /**
-     * Returns the language settings.
-     * @return the languages supported
-     */
-    public String getLanguages() {
-        return "en"; // userLanguages;
-    }
-
-    /**
-     * Returns the media type for this rendering.
-     * @return the media for fo documents is "print"
-     */
-    public String getMedia() {
-        return "print";
-    }
-
-    /**
-     * Returns the user stylesheet uri.
-     * @return null if no user style sheet was specified.
-     */
-    public String getUserStyleSheetURI() {
-        return null; // userStyleSheetURI;
-    }
-
-    /**
-     * Returns the class name of the XML parser.
-     * @return the XML parser class name
-     */
-    public String getXMLParserClassName() {
-        try {
-            SAXParserFactory factory = SAXParserFactory.newInstance();
-            return factory.newSAXParser().getXMLReader().getClass().getName();
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    /**
-     * Is the XML parser validating.
-     * @return true if the xml parser is validating
-     */
-    public boolean isXMLParserValidating() {
-        return false;
-    }
-
-    /**
-     * Get the transform of the svg document.
-     * @return the transform
-     */
-    public AffineTransform getTransform() {
-        return currentTransform;
-    }
-
-    /**
-     * Get the default viewport size for an svg document.
-     * This returns a default value of 100x100.
-     * @return the default viewport size
-     */
-    public Dimension2D getViewportSize() {
-        return new Dimension(100, 100);
-    }
-
-}
-
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java
new file mode 100644 (file)
index 0000000..4df1af3
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.svg;
+
+import java.awt.Dimension;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
+
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.batik.bridge.UserAgentAdapter;
+
+/**
+ * A simple SVG user agent.
+ * This is an implementation of the Batik SVG user agent. It ignores any message output sent
+ * by Batik.
+ */
+public class SimpleSVGUserAgent extends UserAgentAdapter {
+    
+    private AffineTransform currentTransform = null;
+    private float pixelUnitToMillimeter = 0.0f;
+
+    /**
+     * Creates a new user agent.
+     * @param pixelUnitToMM the pixel to millimeter conversion factor currently in effect
+     * @param at the current transform
+     */
+    public SimpleSVGUserAgent(float pixelUnitToMM, AffineTransform at) {
+        pixelUnitToMillimeter = pixelUnitToMM;
+        currentTransform = at;
+    }
+    
+    /**
+     * Returns a customized the pixel to mm factor.
+     * @return the pixel unit to millimeter conversion factor
+     */
+    public float getPixelUnitToMillimeter() {
+        return pixelUnitToMillimeter;
+    }
+
+    /**
+     * Returns the language settings.
+     * @return the languages supported
+     */
+    public String getLanguages() {
+        return "en"; // userLanguages;
+    }
+
+    /**
+     * Returns the media type for this rendering.
+     * @return the media for FO documents is "print"
+     */
+    public String getMedia() {
+        return "print";
+    }
+
+    /**
+     * Returns the user stylesheet URI.
+     * @return null if no user style sheet was specified.
+     */
+    public String getUserStyleSheetURI() {
+        return null; // userStyleSheetURI;
+    }
+
+    /**
+     * Returns the class name of the XML parser.
+     * @return the XML parser class name
+     */
+    public String getXMLParserClassName() {
+        try {
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            return factory.newSAXParser().getXMLReader().getClass().getName();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Is the XML parser validating.
+     * @return true if the XML parser is validating
+     */
+    public boolean isXMLParserValidating() {
+        return false;
+    }
+
+    /**
+     * Get the transform of the SVG document.
+     * @return the transform
+     */
+    public AffineTransform getTransform() {
+        return currentTransform;
+    }
+
+    /** {@inheritDoc} */
+    public void setTransform(AffineTransform at) {
+        this.currentTransform = at;
+    }
+
+    /**
+     * Get the default viewport size for an SVG document.
+     * This returns a default value of 100x100.
+     * @return the default viewport size
+     */
+    public Dimension2D getViewportSize() {
+        return new Dimension(100, 100);
+    }
+
+}
+
index 390e29dd9e192048409933650559e9b3f0c812ce..132f5b4dcc06a2567e01aa5fe24b569befa3131e 100644 (file)
 
 package org.apache.fop.util;
 
-import java.io.Serializable;
-
 /**
  * Represents a qualified name of an XML element or an XML attribute.
  * <p>
  * Note: This class allows to carry a namespace prefix but it is not used in the equals() and 
  * hashCode() methods.
+ * @deprecated Use the XML Graphics Commons variant instead!
  */
-public class QName implements Serializable {
+public class QName extends org.apache.xmlgraphics.util.QName {
 
     private static final long serialVersionUID = -5225376740044770690L;
     
-    private String namespaceURI;
-    private String localName;
-    private String prefix;
-    private int hashCode;
-    
     /**
      * Main constructor.
      * @param namespaceURI the namespace URI
@@ -43,16 +37,7 @@ public class QName implements Serializable {
      * @param localName the local name
      */
     public QName(String namespaceURI, String prefix, String localName) {
-        if (localName == null) {
-            throw new NullPointerException("Parameter localName must not be null");
-        }
-        if (localName.length() == 0) {
-            throw new IllegalArgumentException("Parameter localName must not be empty");
-        }
-        this.namespaceURI = namespaceURI;
-        this.prefix = prefix;
-        this.localName = localName;
-        this.hashCode = toHashString().hashCode();
+        super(namespaceURI, prefix, localName);
     }
     
     /**
@@ -61,78 +46,7 @@ public class QName implements Serializable {
      * @param qName the qualified name
      */
     public QName(String namespaceURI, String qName) {
-        if (qName == null) {
-            throw new NullPointerException("Parameter localName must not be null");
-        }
-        if (qName.length() == 0) {
-            throw new IllegalArgumentException("Parameter localName must not be empty");
-        }
-        this.namespaceURI = namespaceURI;
-        int p = qName.indexOf(':');
-        if (p > 0) {
-            this.prefix = qName.substring(0, p);
-            this.localName = qName.substring(p + 1);
-        } else {
-            this.prefix = null;
-            this.localName = qName;
-        }
-        this.hashCode = toHashString().hashCode();
+        super(namespaceURI, qName);
     }
     
-    /** @return the namespace URI */
-    public String getNamespaceURI() {
-        return this.namespaceURI;
-    }
-    
-    /** @return the namespace prefix */
-    public String getPrefix() {
-        return this.prefix;
-    }
-    
-    /** @return the local name */
-    public String getLocalName() {
-        return this.localName;
-    }
-    
-    /** @return the fully qualified name */
-    public String getQName() {
-        return getPrefix() != null ? getPrefix() + ':' + getLocalName() : getLocalName();
-    }
-
-    /** {@inheritDoc} */
-    public int hashCode() {
-        return this.hashCode;
-    }
-
-    /** {@inheritDoc} */
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        } else if (obj == this) {
-            return true;
-        } else {
-            if (obj instanceof QName) {
-                QName other = (QName)obj;
-                if ((getNamespaceURI() == null && other.getNamespaceURI() == null)
-                        || getNamespaceURI().equals(other.getNamespaceURI())) {
-                    return getLocalName().equals(other.getLocalName());
-                }
-            }
-        }
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    public String toString() {
-        return prefix != null
-                ? (prefix + ":" + localName)
-                : toHashString();
-    }
-
-    private String toHashString() {
-        return (namespaceURI != null 
-                ? ("{" + namespaceURI + "}" + localName) 
-                : localName);
-    }
-
 }
diff --git a/src/java/org/apache/fop/util/XMLResourceBundle.java b/src/java/org/apache/fop/util/XMLResourceBundle.java
new file mode 100644 (file)
index 0000000..1b32081
--- /dev/null
@@ -0,0 +1,398 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Stack;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.stream.StreamSource;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.xmlgraphics.util.QName;
+
+/**
+ * This class is a ResourceBundle that loads its contents from XML files instead of properties
+ * files (like PropertiesResourceBundle).
+ * <p>
+ * The XML format for this resource bundle implementation is the following
+ * (the same as Apache Cocoon's XMLResourceBundle):
+ * <pre>
+ * &lt;catalogue xml:lang="en"&gt;
+ *   &lt;message key="key1"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ *   &lt;message key="key2"&gt;Message &lt;br/&gt; Value 1&lt;/message&gt;
+ *   ...
+ * &lt;/catalogue&gt;
+ * </pre>
+ */
+public class XMLResourceBundle extends ResourceBundle {
+
+    //Note: Some code here has been copied and adapted from Apache Harmony!
+    
+    private Properties resources = new Properties();
+
+    private Locale locale;
+    
+    private static SAXTransformerFactory tFactory 
+        = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+    /**
+     * Creates a resource bundle from an InputStream.
+     * @param in the stream to read from
+     * @throws IOException if an I/O error occurs
+     */
+    public XMLResourceBundle(InputStream in) throws IOException {
+        try {
+            Transformer transformer = tFactory.newTransformer();
+            StreamSource src = new StreamSource(in);
+            SAXResult res = new SAXResult(new CatalogueHandler());
+            transformer.transform(src, res);
+        } catch (TransformerException e) {
+            throw new IOException("Error while parsing XML resource bundle: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * Gets a resource bundle using the specified base name, default locale, and class loader.
+     * @param baseName the base name of the resource bundle, a fully qualified class name
+     * @param loader the class loader from which to load the resource bundle
+     * @return a resource bundle for the given base name and the default locale
+     * @throws MissingResourceException if no resource bundle for the specified base name can be
+     *                                          found
+     * @see java.util.ResourceBundle#getBundle(String)
+     */
+    public static ResourceBundle getXMLBundle(String baseName, ClassLoader loader)
+                throws MissingResourceException {
+        return getXMLBundle(baseName, Locale.getDefault(), loader);
+    }
+    
+    /**
+     * Gets a resource bundle using the specified base name, locale, and class loader.
+     * @param baseName the base name of the resource bundle, a fully qualified class name
+     * @param locale the locale for which a resource bundle is desired
+     * @param loader the class loader from which to load the resource bundle
+     * @return a resource bundle for the given base name and locale
+     * @throws MissingResourceException if no resource bundle for the specified base name can be
+     *                                          found
+     * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader)
+     */
+    public static ResourceBundle getXMLBundle(String baseName, Locale locale, ClassLoader loader)
+                throws MissingResourceException {
+        if (loader == null) {
+            throw new NullPointerException("loader must not be null");
+        }
+        if (baseName == null) {
+            throw new NullPointerException("baseName must not be null");
+        }
+            
+        ResourceBundle bundle;
+        if (!locale.equals(Locale.getDefault())) {
+            bundle = handleGetXMLBundle(baseName, "_" + locale, false, loader);
+            if (bundle != null) {
+                return bundle;
+            }
+        }
+        bundle = handleGetXMLBundle(baseName, "_" + Locale.getDefault(), true, loader);
+        if (bundle != null) {
+            return bundle;
+        }
+        throw new MissingResourceException(
+                baseName + " (" + locale + ")", baseName + '_' + locale, null);
+    }
+
+    static class MissingBundle extends ResourceBundle {
+        public Enumeration getKeys() {
+            return null;
+        }
+
+        public Object handleGetObject(String name) {
+            return null;
+        }
+    }
+
+    private static final ResourceBundle MISSING = new MissingBundle();
+    private static final ResourceBundle MISSINGBASE = new MissingBundle();
+    
+    private static Map cache = new java.util.WeakHashMap();
+    //<Object, Hashtable<String, ResourceBundle>>
+    
+    private static ResourceBundle handleGetXMLBundle(String base, String locale,
+            boolean loadBase, final ClassLoader loader) {
+        XMLResourceBundle bundle = null;
+        String bundleName = base + locale;
+        Object cacheKey = loader != null ? (Object) loader : (Object) "null";
+        Hashtable loaderCache; //<String, ResourceBundle>
+        synchronized (cache) {
+            loaderCache = (Hashtable)cache.get(cacheKey);
+            if (loaderCache == null) {
+                loaderCache = new Hashtable();
+                cache.put(cacheKey, loaderCache);
+            }
+        }
+        ResourceBundle result = (ResourceBundle)loaderCache.get(bundleName);
+        if (result != null) {
+            if (result == MISSINGBASE) {
+                return null;
+            }
+            if (result == MISSING) {
+                if (!loadBase) {
+                    return null;
+                }
+                String extension = strip(locale);
+                if (extension == null) {
+                    return null;
+                }
+                return handleGetXMLBundle(base, extension, loadBase, loader);
+            }
+            return result;
+        }
+
+        final String fileName = bundleName.replace('.', '/') + ".xml";
+        InputStream stream = (InputStream)AccessController
+                .doPrivileged(new PrivilegedAction() {
+                    public Object run() {
+                        return loader == null
+                            ? ClassLoader.getSystemResourceAsStream(fileName)
+                            : loader.getResourceAsStream(fileName);
+                    }
+                });
+        if (stream != null) {
+            try {
+                try {
+                    bundle = new XMLResourceBundle(stream);
+                } finally {
+                    stream.close();
+                }
+                bundle.setLocale(locale);
+            } catch (IOException e) {
+                throw new MissingResourceException(e.getMessage(), base, null);
+            }
+        }
+
+        String extension = strip(locale);
+        if (bundle != null) {
+            if (extension != null) {
+                ResourceBundle parent = handleGetXMLBundle(base, extension, true,
+                        loader);
+                if (parent != null) {
+                    bundle.setParent(parent);
+                }
+            }
+            loaderCache.put(bundleName, bundle);
+            return bundle;
+        }
+
+        if (extension != null) {
+            ResourceBundle fallback = handleGetXMLBundle(base, extension, loadBase, loader);
+            if (fallback != null) {
+                loaderCache.put(bundleName, fallback);
+                return fallback;
+            }
+        }
+        loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING);
+        return null;
+    }    
+    
+    private void setLocale(String name) {
+        String language = "", country = "", variant = "";
+        if (name.length() > 1) {
+            int nextIndex = name.indexOf('_', 1);
+            if (nextIndex == -1) {
+                nextIndex = name.length();
+            }
+            language = name.substring(1, nextIndex);
+            if (nextIndex + 1 < name.length()) {
+                int index = nextIndex;
+                nextIndex = name.indexOf('_', nextIndex + 1);
+                if (nextIndex == -1) {
+                    nextIndex = name.length();
+                }
+                country = name.substring(index + 1, nextIndex);
+                if (nextIndex + 1 < name.length()) {
+                    variant = name.substring(nextIndex + 1, name.length());
+                }
+            }
+        }
+        this.locale = new Locale(language, country, variant);
+    }
+    
+    private static String strip(String name) {
+        int index = name.lastIndexOf('_');
+        if (index != -1) {
+            return name.substring(0, index);
+        }
+        return null;
+    }
+    
+    private Enumeration getLocalKeys() {
+        return (Enumeration)resources.propertyNames();
+    }
+    
+    /** {@inheritDoc} */
+    public Locale getLocale() {
+        return this.locale;
+    }
+    
+    /** {@inheritDoc} */
+    public Enumeration getKeys() {
+        if (parent == null) {
+            return getLocalKeys();
+        }
+        return new Enumeration() {
+            private Enumeration local = getLocalKeys();
+            private Enumeration pEnum = parent.getKeys();
+
+            private Object nextElement;
+
+            private boolean findNext() {
+                if (nextElement != null) {
+                    return true;
+                }
+                while (pEnum.hasMoreElements()) {
+                    Object next = pEnum.nextElement();
+                    if (!resources.containsKey(next)) {
+                        nextElement = next;
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            public boolean hasMoreElements() {
+                if (local.hasMoreElements()) {
+                    return true;
+                }
+                return findNext();
+            }
+
+            public Object nextElement() {
+                if (local.hasMoreElements()) {
+                    return local.nextElement();
+                }
+                if (findNext()) {
+                    Object result = nextElement;
+                    nextElement = null;
+                    return result;
+                }
+                // Cause an exception
+                return pEnum.nextElement();
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    protected Object handleGetObject(String key) {
+        if (key == null) {
+            throw new NullPointerException("key must not be null");
+        }
+        return resources.get(key);
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "XMLResourceBundle: " + getLocale();
+    }
+
+    private class CatalogueHandler extends DefaultHandler {
+        
+        private static final String CATALOGUE = "catalogue";
+        private static final String MESSAGE = "message";
+        
+        private StringBuffer valueBuffer = new StringBuffer();
+        private Stack elementStack = new Stack();
+        private String currentKey = null;
+
+        private boolean isOwnNamespace(String uri) {
+            return ("".equals(uri));
+        }
+        
+        private QName getParentElementName() {
+            return (QName)elementStack.peek();
+        }
+        
+        /** {@inheritDoc} */
+        public void startElement(String uri, String localName, String qName, 
+                Attributes atts) throws SAXException {
+            super.startElement(uri, localName, qName, atts);
+            QName elementName = new QName(uri, qName);
+            if (isOwnNamespace(uri)) {
+                if (CATALOGUE.equals(localName)) {
+                    //nop
+                } else if (MESSAGE.equals(localName)) {
+                    if (!CATALOGUE.equals(getParentElementName().getLocalName())) {
+                        throw new SAXException(MESSAGE + " must be a child of " + CATALOGUE);
+                    }
+                    this.currentKey = atts.getValue("key");
+                } else {
+                    throw new SAXException("Invalid element name: " + elementName);
+                }
+            } else {
+                //ignore
+            }
+            this.valueBuffer.setLength(0);
+            elementStack.push(elementName);
+        }
+
+        /** {@inheritDoc} */
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            super.endElement(uri, localName, qName);
+            elementStack.pop();
+            if (isOwnNamespace(uri)) {
+                if (CATALOGUE.equals(localName)) {
+                    //nop
+                } else if (MESSAGE.equals(localName)) {
+                    if (this.currentKey == null) {
+                        throw new SAXException(
+                                "current key is null (attribute 'key' might be mistyped)");
+                    }
+                    resources.put(this.currentKey, this.valueBuffer.toString());
+                    this.currentKey = null;
+                }
+            } else {
+                //ignore
+            }
+            this.valueBuffer.setLength(0);
+        }
+        
+        /** {@inheritDoc} */
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            super.characters(ch, start, length);
+            valueBuffer.append(ch, start, length);
+        }
+        
+    }
+    
+}
diff --git a/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java b/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java
new file mode 100644 (file)
index 0000000..a216915
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.xmlgraphics.util.Service;
+
+
+/**
+ * Formats messages based on a template and with a set of named parameters. This is similar to
+ * {@link java.util.MessageFormat} but uses named parameters and supports conditional sub-groups.
+ * <p>
+ * Example:
+ * </p>
+ * <p><code>Missing field "{fieldName}"[ at location: {location}]!</code></p>
+ * <ul>
+ *   <li>Curly brackets ("{}") are used for fields.</li>
+ *   <li>Square brackets ("[]") are used to delimit conditional sub-groups. A sub-group is
+ *     conditional when all fields inside the sub-group have a null value. In the case, everything
+ *     between the brackets is skipped.</li>
+ * </ul>
+ */
+public class AdvancedMessageFormat {
+
+    /** Regex that matches "," but not "\," (escaped comma) */
+    static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(?<!\\\\),");
+    
+    private static final Map PART_FACTORIES = new java.util.HashMap();
+    private static final List OBJECT_FORMATTERS = new java.util.ArrayList();
+    private static final Map FUNCTIONS = new java.util.HashMap();
+    
+    private CompositePart rootPart;
+    
+    static {
+        Iterator iter;
+        iter = Service.providers(PartFactory.class, true);
+        while (iter.hasNext()) {
+            PartFactory factory = (PartFactory)iter.next();
+            PART_FACTORIES.put(factory.getFormat(), factory);
+        }
+        iter = Service.providers(ObjectFormatter.class, true);
+        while (iter.hasNext()) {
+            OBJECT_FORMATTERS.add((ObjectFormatter)iter.next());
+        }
+        iter = Service.providers(Function.class, true);
+        while (iter.hasNext()) {
+            Function function = (Function)iter.next();
+            FUNCTIONS.put(function.getName(), function);
+        }
+    }
+    
+    /**
+     * Construct a new message format.
+     * @param pattern the message format pattern.
+     */
+    public AdvancedMessageFormat(CharSequence pattern) {
+        parsePattern(pattern);
+    }
+    
+    private void parsePattern(CharSequence pattern) {
+        rootPart = new CompositePart(false);
+        StringBuffer sb = new StringBuffer();
+        parseInnerPattern(pattern, rootPart, sb, 0);
+    }
+    
+    private int parseInnerPattern(CharSequence pattern, CompositePart parent,
+            StringBuffer sb, int start) {
+        assert sb.length() == 0;
+        int i = start;
+        int len = pattern.length();
+        loop:
+        while (i < len) {
+            char ch = pattern.charAt(i);
+            switch (ch) {
+            case '{':
+                if (sb.length() > 0) {
+                    parent.addChild(new TextPart(sb.toString()));
+                    sb.setLength(0);
+                }
+                i++;
+                int nesting = 1;
+                while (i < len) {
+                    ch = pattern.charAt(i);
+                    if (ch == '{') {
+                        nesting++;
+                    } else if (ch == '}') {
+                        nesting--;
+                        if (nesting == 0) {
+                            i++;
+                            break;
+                        }
+                    }
+                    sb.append(ch);
+                    i++;
+                }
+                parent.addChild(parseField(sb.toString()));
+                sb.setLength(0);
+                break;
+            case ']':
+                i++;
+                break loop; //Current composite is finished
+            case '[':
+                if (sb.length() > 0) {
+                    parent.addChild(new TextPart(sb.toString()));
+                    sb.setLength(0);
+                }
+                i++;
+                CompositePart composite = new CompositePart(true);
+                parent.addChild(composite);
+                i += parseInnerPattern(pattern, composite, sb, i);
+                break;
+            case '|':
+                if (sb.length() > 0) {
+                    parent.addChild(new TextPart(sb.toString()));
+                    sb.setLength(0);
+                }
+                parent.newSection();
+                i++;
+                break;
+            case '\\':
+                if (i < len - 1) {
+                    i++;
+                    ch = pattern.charAt(i);
+                }
+                //no break here! Must be right before "default" section
+            default:
+                sb.append(ch);
+                i++;
+            }
+        }
+        if (sb.length() > 0) {
+            parent.addChild(new TextPart(sb.toString()));
+            sb.setLength(0);
+        }
+        return i - start;
+    }
+    
+    private Part parseField(String field) {
+        String[] parts = COMMA_SEPARATOR_REGEX.split(field, 3);
+        String fieldName = parts[0];
+        if (parts.length == 1) {
+            if (fieldName.startsWith("#")) {
+                return new FunctionPart(fieldName.substring(1));
+            } else {
+                return new SimpleFieldPart(fieldName);
+            }
+        } else {
+            String format = parts[1];
+            PartFactory factory = (PartFactory)PART_FACTORIES.get(format);
+            if (factory == null) {
+                throw new IllegalArgumentException(
+                        "No PartFactory available under the name: " + format);
+            }
+            if (parts.length == 2) {
+                return factory.newPart(fieldName, null);
+            } else {
+                return factory.newPart(fieldName, parts[2]);
+            }
+        }
+    }
+
+    private static Function getFunction(String functionName) {
+        return (Function)FUNCTIONS.get(functionName);
+    }
+
+    /**
+     * Formats a message with the given parameters.
+     * @param params a Map of named parameters (Contents: <String, Object>)
+     * @return the formatted message
+     */
+    public String format(Map params) {
+        StringBuffer sb = new StringBuffer();
+        format(params, sb);
+        return sb.toString();
+    }
+
+    /**
+     * Formats a message with the given parameters.
+     * @param params a Map of named parameters (Contents: <String, Object>)
+     * @param target the target StringBuffer to write the formatted message to
+     */
+    public void format(Map params, StringBuffer target) {
+        rootPart.write(target, params);
+    }
+    
+    /**
+     * Represents a message template part. This interface is implemented by various variants of
+     * the single curly braces pattern ({field}, {field,if,yes,no} etc.).
+     */
+    public interface Part {
+        
+        /**
+         * Writes the formatted part to a string buffer.
+         * @param sb the target string buffer
+         * @param params the parameters to work with
+         */
+        void write(StringBuffer sb, Map params);
+        
+        /**
+         * Indicates whether there is any content that is generated by this message part.
+         * @param params the parameters to work with
+         * @return true if the part has content
+         */
+        boolean isGenerated(Map params);
+    }
+    
+    /**
+     * Implementations of this interface parse a field part and return message parts.
+     */
+    public interface PartFactory {
+        
+        /**
+         * Creates a new part by parsing the values parameter to configure the part.
+         * @param fieldName the field name
+         * @param values the unparsed parameter values
+         * @return the new message part
+         */
+        Part newPart(String fieldName, String values);
+        
+        /**
+         * Returns the name of the message part format.
+         * @return the name of the message part format
+         */
+        String getFormat();
+    }
+    
+    /**
+     * Implementations of this interface format certain objects to strings.
+     */
+    public interface ObjectFormatter {
+        
+        /**
+         * Formats an object to a string and writes the result to a string buffer.
+         * @param sb the target string buffer
+         * @param obj the object to be formatted
+         */
+        void format(StringBuffer sb, Object obj);
+        
+        /**
+         * Indicates whether a given object is supported.
+         * @param obj the object
+         * @return true if the object is supported by the formatter
+         */
+        boolean supportsObject(Object obj);
+    }
+    
+    /**
+     * Implementations of this interface do some computation based on the message parameters
+     * given to it. Note: at the moment, this has to be done in a local-independent way since
+     * there is no locale information.
+     */
+    public interface Function {
+        
+        /**
+         * Executes the function.
+         * @param params the message parameters
+         * @return the function result
+         */
+        Object evaluate(Map params);
+        
+        /**
+         * Returns the name of the function.
+         * @return the name of the function
+         */
+        Object getName();
+    }
+    
+    private static class TextPart implements Part {
+        
+        private String text;
+        
+        public TextPart(String text) {
+            this.text = text;
+        }
+        
+        public void write(StringBuffer sb, Map params) {
+            sb.append(text);
+        }
+        
+        public boolean isGenerated(Map params) {
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public String toString() {
+            return this.text;
+        }
+    }
+    
+    private static class SimpleFieldPart implements Part {
+        
+        private String fieldName;
+        
+        public SimpleFieldPart(String fieldName) {
+            this.fieldName = fieldName;
+        }
+        
+        public void write(StringBuffer sb, Map params) {
+            if (!params.containsKey(fieldName)) {
+                throw new IllegalArgumentException(
+                        "Message pattern contains unsupported field name: " + fieldName);
+            }
+            Object obj = params.get(fieldName);
+            formatObject(obj, sb);
+        }
+
+        public boolean isGenerated(Map params) {
+            Object obj = params.get(fieldName);
+            return obj != null;
+        }
+        
+        /** {@inheritDoc} */
+        public String toString() {
+            return "{" + this.fieldName + "}";
+        }
+    }
+    
+    /**
+     * Formats an object to a string and writes the result to a string buffer. This method
+     * usually uses the object's <code>toString()</code> method unless there is an
+     * {@link ObjectFormatter} that supports the object. {@link ObjectFormatter}s are registered
+     * through the service provider mechanism defined by the JAR specification.
+     * @param obj the object to be formatted
+     * @param target the target string buffer
+     */
+    public static void formatObject(Object obj, StringBuffer target) {
+        if (obj instanceof String) {
+            target.append(obj);
+        } else {
+            boolean handled = false;
+            Iterator iter = OBJECT_FORMATTERS.iterator();
+            while (iter.hasNext()) {
+                ObjectFormatter formatter = (ObjectFormatter)iter.next();
+                if (formatter.supportsObject(obj)) {
+                    formatter.format(target, obj);
+                    handled = true;
+                    break;
+                }
+            }
+            if (!handled) {
+                target.append(String.valueOf(obj));
+            }
+        }
+    }
+    
+    private static class FunctionPart implements Part {
+        
+        private Function function;
+        
+        public FunctionPart(String functionName) {
+            this.function = getFunction(functionName);
+            if (this.function == null) {
+                throw new IllegalArgumentException("Unknown function: " + functionName);
+            }
+        }
+        
+        public void write(StringBuffer sb, Map params) {
+            Object obj = this.function.evaluate(params);
+            formatObject(obj, sb);
+        }
+
+        public boolean isGenerated(Map params) {
+            Object obj = this.function.evaluate(params);
+            return obj != null;
+        }
+        
+        /** {@inheritDoc} */
+        public String toString() {
+            return "{#" + this.function.getName() + "}";
+        }
+    }
+    
+    private static class CompositePart implements Part {
+        
+        protected List parts = new java.util.ArrayList();
+        private boolean conditional;
+        private boolean hasSections = false;
+        
+        public CompositePart(boolean conditional) {
+            this.conditional = conditional;
+        }
+        
+        private CompositePart(List parts) {
+            this.parts.addAll(parts);
+            this.conditional = true;
+        }
+        
+        public void addChild(Part part) {
+            if (part == null) {
+                throw new NullPointerException("part must not be null");
+            }
+            if (hasSections) {
+                CompositePart composite = (CompositePart)this.parts.get(this.parts.size() - 1);
+                composite.addChild(part);
+            } else {
+                this.parts.add(part);
+            }
+        }
+        
+        public void newSection() {
+            if (!hasSections) {
+                List p = this.parts;
+                //Dropping into a different mode...
+                this.parts = new java.util.ArrayList();
+                this.parts.add(new CompositePart(p));
+                hasSections = true;
+            }
+            this.parts.add(new CompositePart(true));
+        }
+        
+        public void write(StringBuffer sb, Map params) {
+            if (hasSections) {
+                Iterator iter = this.parts.iterator();
+                while (iter.hasNext()) {
+                    CompositePart part = (CompositePart)iter.next();
+                    if (part.isGenerated(params)) {
+                        part.write(sb, params);
+                        break;
+                    }
+                }
+            } else {
+                if (isGenerated(params)) {
+                    Iterator iter = this.parts.iterator();
+                    while (iter.hasNext()) {
+                        Part part = (Part)iter.next();
+                        part.write(sb, params);
+                    }
+                }
+            }
+        }
+
+        public boolean isGenerated(Map params) {
+            if (hasSections) {
+                Iterator iter = this.parts.iterator();
+                while (iter.hasNext()) {
+                    Part part = (Part)iter.next();
+                    if (part.isGenerated(params)) {
+                        return true;
+                    }
+                }
+                return false;
+            } else {
+                if (conditional) {
+                    Iterator iter = this.parts.iterator();
+                    while (iter.hasNext()) {
+                        Part part = (Part)iter.next();
+                        if (!part.isGenerated(params)) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+        
+        /** {@inheritDoc} */
+        public String toString() {
+            return this.parts.toString();
+        }
+    }
+    
+    
+    static String unescapeComma(String string) {
+        return string.replaceAll("\\\\,", ",");
+    }
+}
diff --git a/src/java/org/apache/fop/util/text/ChoiceFieldPart.java b/src/java/org/apache/fop/util/text/ChoiceFieldPart.java
new file mode 100644 (file)
index 0000000..df457a0
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.text.ChoiceFormat;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines a "choice" field part that works like {@link ChoiceFormat}.
+ */
+public class ChoiceFieldPart implements Part {
+    
+    private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([^\\}]+)\\}");
+    
+    private String fieldName;
+    private ChoiceFormat choiceFormat;
+    
+    /**
+     * Creates a new choice part.
+     * @param fieldName the field name to work on
+     * @param choicesPattern the choices pattern (as used by {@link ChoiceFormat})
+     */
+    public ChoiceFieldPart(String fieldName, String choicesPattern) {
+        this.fieldName = fieldName;
+        this.choiceFormat = new ChoiceFormat(choicesPattern);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isGenerated(Map params) {
+        Object obj = params.get(fieldName);
+        return obj != null;
+    }
+
+    /** {@inheritDoc} */
+    public void write(StringBuffer sb, Map params) {
+        Object obj = params.get(fieldName);
+        Number num = (Number)obj;
+        String result = this.choiceFormat.format(num.doubleValue());
+        Matcher m = VARIABLE_REGEX.matcher(result);
+        if (m.find()) {
+            //Resolve inner variables
+            AdvancedMessageFormat f = new AdvancedMessageFormat(result);
+            f.format(params, sb);
+        } else {
+            sb.append(result);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{" + this.fieldName + ",choice, ....}";
+    }
+    
+    /** Factory for ChoiceFieldPart. */
+    public static class Factory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new ChoiceFieldPart(fieldName, values);
+        }
+
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "choice";
+        }
+        
+    }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/EqualsFieldPart.java b/src/java/org/apache/fop/util/text/EqualsFieldPart.java
new file mode 100644 (file)
index 0000000..2114b0d
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines an "equals" field part that can compare a field's string value against another string.
+ * It returns either of two possible values attached as additional part parameters. Example:
+ * <code>{field,equals,new,This is new!,This is old!}</code>
+ */
+public class EqualsFieldPart extends IfFieldPart {
+    
+    private String equalsValue;
+    
+    /**
+     * Creates a new "equals" field part.
+     * @param fieldName the field name
+     * @param values the unparsed parameter values
+     */
+    public EqualsFieldPart(String fieldName, String values) {
+        super(fieldName, values);
+    }
+
+    /** {@inheritDoc} */
+    protected void parseValues(String values) {
+        String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 3);
+        this.equalsValue = parts[0];
+        if (parts.length == 1) {
+            throw new IllegalArgumentException(
+                    "'equals' format must have at least 2 parameters");
+        }
+        if (parts.length == 3) {
+            ifValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+            elseValue = AdvancedMessageFormat.unescapeComma(parts[2]);
+        } else {
+            ifValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+        }
+    }
+    
+    /** {@inheritDoc} */
+    protected boolean isTrue(Map params) {
+        Object obj = params.get(fieldName);
+        if (obj != null) {
+            return String.valueOf(obj).equals(this.equalsValue);
+        } else {
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{" + this.fieldName + ", equals " + this.equalsValue + "}";
+    }
+    
+    /**
+     * Part factory for "equals".
+     */
+    public static class Factory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new EqualsFieldPart(fieldName, values);
+        }
+
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "equals";
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java b/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java
new file mode 100644 (file)
index 0000000..5d78cdf
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.xmlgraphics.fonts.Glyphs;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Function formatting a character to a glyph name.
+ */
+public class GlyphNameFieldPart implements Part {
+
+    private String fieldName;
+    
+    /**
+     * Creates a new glyph name field part
+     * @param fieldName the field name
+     */
+    public GlyphNameFieldPart(String fieldName) {
+        this.fieldName = fieldName;
+    }
+    
+    /** {@inheritDoc} */
+    public boolean isGenerated(Map params) {
+        Object obj = params.get(fieldName);
+        return obj != null && getGlyphName(obj).length() > 0;
+    }
+    
+    private String getGlyphName(Object obj) {
+        if (obj instanceof Character) {
+            return Glyphs.charToGlyphName(((Character)obj).charValue());
+        } else {
+            throw new IllegalArgumentException(
+                    "Value for glyph name part must be a Character but was: "
+                        + obj.getClass().getName());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void write(StringBuffer sb, Map params) {
+        if (!params.containsKey(fieldName)) {
+            throw new IllegalArgumentException(
+                    "Message pattern contains unsupported field name: " + fieldName);
+        }
+        Object obj = params.get(fieldName);
+        sb.append(getGlyphName(obj));
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{" + this.fieldName + ",glyph-name}";
+    }
+    
+    /** Factory for {@link GlyphNameFieldPart}. */
+    public static class Factory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new GlyphNameFieldPart(fieldName);
+        }
+        
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "glyph-name";
+        }
+        
+    }
+}
diff --git a/src/java/org/apache/fop/util/text/HexFieldPart.java b/src/java/org/apache/fop/util/text/HexFieldPart.java
new file mode 100644 (file)
index 0000000..19f47f3
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Function formatting a number or character to a hex value.
+ */
+public class HexFieldPart implements Part {
+
+    private String fieldName;
+    
+    /**
+     * Creates a new hex field part
+     * @param fieldName the field name
+     */
+    public HexFieldPart(String fieldName) {
+        this.fieldName = fieldName;
+    }
+    
+    /** {@inheritDoc} */
+    public boolean isGenerated(Map params) {
+        Object obj = params.get(fieldName);
+        return obj != null;
+    }
+
+    /** {@inheritDoc} */
+    public void write(StringBuffer sb, Map params) {
+        if (!params.containsKey(fieldName)) {
+            throw new IllegalArgumentException(
+                    "Message pattern contains unsupported field name: " + fieldName);
+        }
+        Object obj = params.get(fieldName);
+        if (obj instanceof Character) {
+            sb.append(Integer.toHexString(((Character)obj).charValue()));
+        } else if (obj instanceof Number) {
+            sb.append(Integer.toHexString(((Number)obj).intValue()));
+        } else {
+            throw new IllegalArgumentException("Incompatible value for hex field part: "
+                    + obj.getClass().getName());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{" + this.fieldName + ",hex}";
+    }
+    
+    /** Factory for {@link HexFieldPart}. */
+    public static class Factory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new HexFieldPart(fieldName);
+        }
+        
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "hex";
+        }
+        
+    }
+}
diff --git a/src/java/org/apache/fop/util/text/IfFieldPart.java b/src/java/org/apache/fop/util/text/IfFieldPart.java
new file mode 100644 (file)
index 0000000..31cd8f3
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import java.util.Map;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.Part;
+import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory;
+
+/**
+ * Defines an "if" field part that checks if field's value is true or false.
+ * It returns either of two possible values attached as additional part parameters. Example:
+ * <code>{field,if,Yes,No}</code>
+ */
+public class IfFieldPart implements Part {
+    
+    /** the field name for the part */
+    protected String fieldName;
+    /** the value being returned if the field is true */
+    protected String ifValue;
+    /** the value being returned if the field is false */
+    protected String elseValue;
+    
+    /**
+     * Creates a new "if" field part.
+     * @param fieldName the field name
+     * @param values the unparsed parameter values
+     */
+    public IfFieldPart(String fieldName, String values) {
+        this.fieldName = fieldName;
+        parseValues(values);
+    }
+
+    /**
+     * Parses the parameter values
+     * @param values the unparsed parameter values
+     */
+    protected void parseValues(String values) {
+        String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 2);
+        if (parts.length == 2) {
+            ifValue = AdvancedMessageFormat.unescapeComma(parts[0]);
+            elseValue = AdvancedMessageFormat.unescapeComma(parts[1]);
+        } else {
+            ifValue = AdvancedMessageFormat.unescapeComma(values);
+        }
+    }
+    
+    /** {@inheritDoc} */
+    public void write(StringBuffer sb, Map params) {
+        boolean isTrue = isTrue(params);
+        if (isTrue) {
+            sb.append(ifValue);
+        } else if (elseValue != null) {
+            sb.append(elseValue);
+        }
+    }
+
+    /**
+     * Indicates whether the field's value is true. If the field is not a boolen, it is true
+     * if the field is not null.
+     * @param params the message parameters
+     * @return true the field's value as boolean
+     */
+    protected boolean isTrue(Map params) {
+        Object obj = params.get(fieldName);
+        if (obj instanceof Boolean) {
+            return ((Boolean)obj).booleanValue();
+        } else {
+            return (obj != null);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public boolean isGenerated(Map params) {
+        return isTrue(params) || (elseValue != null);
+    }
+    
+    /** {@inheritDoc} */
+    public String toString() {
+        return "{" + this.fieldName + ", if...}";
+    }
+    
+    /**
+     * Part factory for "if".
+     */
+    public static class Factory implements PartFactory {
+
+        /** {@inheritDoc} */
+        public Part newPart(String fieldName, String values) {
+            return new IfFieldPart(fieldName, values);
+        }
+        
+        /** {@inheritDoc} */
+        public String getFormat() {
+            return "if";
+        }
+        
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/fop/util/text/LocatorFormatter.java b/src/java/org/apache/fop/util/text/LocatorFormatter.java
new file mode 100644 (file)
index 0000000..d9532c6
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util.text;
+
+import org.xml.sax.Locator;
+
+import org.apache.fop.util.text.AdvancedMessageFormat.ObjectFormatter;
+
+/**
+ * Object formatter for the SAX Locator object.
+ */
+public class LocatorFormatter implements ObjectFormatter {
+
+    /** {@inheritDoc} */
+    public void format(StringBuffer sb, Object obj) {
+        Locator loc = (Locator)obj;
+        sb.append(loc.getLineNumber()).append(":").append(loc.getColumnNumber());
+    }
+
+    /** {@inheritDoc} */
+    public boolean supportsObject(Object obj) {
+        return obj instanceof Locator;
+    }
+    
+}
\ No newline at end of file
diff --git a/test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory b/test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory
new file mode 100644 (file)
index 0000000..a40d565
--- /dev/null
@@ -0,0 +1 @@
+org.apache.fop.events.FOPTestEventModelFactory
\ No newline at end of file
index 86a3469ceb86b2a4d09a7aafa2651b28f21f7fc9..d2577c251ad109894e1bbc3afb565ea2eec78a0d 100644 (file)
@@ -23,12 +23,14 @@ import junit.framework.Test;
 import junit.framework.TestSuite;
 
 import org.apache.fop.pdf.PDFObjectTestCase;
+import org.apache.fop.events.BasicEventTestCase;
 import org.apache.fop.traits.BorderPropsTestCase;
 import org.apache.fop.util.DataURIResolverTestCase;
 import org.apache.fop.util.ElementListUtilsTestCase;
 import org.apache.fop.util.PDFNumberTestCase;
 import org.apache.fop.util.ColorUtilTestCase;
 import org.apache.fop.util.UnitConvTestCase;
+import org.apache.fop.util.XMLResourceBundleTestCase;
 
 /**
  * Test suite for FOP's utility classes.
@@ -50,6 +52,8 @@ public class UtilityCodeTestSuite {
         suite.addTest(new TestSuite(BorderPropsTestCase.class));
         suite.addTest(new TestSuite(ElementListUtilsTestCase.class));
         suite.addTest(new TestSuite(DataURIResolverTestCase.class));
+        suite.addTest(new TestSuite(BasicEventTestCase.class));
+        suite.addTest(new TestSuite(XMLResourceBundleTestCase.class));
         //$JUnit-END$
         return suite;
     }
diff --git a/test/java/org/apache/fop/events/BasicEventTestCase.java b/test/java/org/apache/fop/events/BasicEventTestCase.java
new file mode 100644 (file)
index 0000000..d365ca0
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import junit.framework.TestCase;
+
+import org.apache.fop.events.model.EventSeverity;
+
+public class BasicEventTestCase extends TestCase {
+
+    public void testBasics() throws Exception {
+        
+        MyEventListener listener = new MyEventListener();
+
+        EventBroadcaster broadcaster = new DefaultEventBroadcaster();
+        broadcaster.addEventListener(listener);
+        assertTrue(broadcaster.hasEventListeners());
+        
+        Event ev = new Event(this, "123", EventSeverity.INFO,
+                Event.paramsBuilder()
+                    .param("reason", "I'm tired")
+                    .param("blah", new Integer(23))
+                    .build());
+        broadcaster.broadcastEvent(ev);
+        
+        ev = listener.event;
+        assertNotNull(ev);
+        assertEquals("123", listener.event.getEventID());
+        assertEquals(EventSeverity.INFO, listener.event.getSeverity());
+        assertEquals("I'm tired", ev.getParam("reason"));
+        assertEquals(new Integer(23), ev.getParam("blah"));
+        
+        broadcaster.removeEventListener(listener);
+        assertFalse(broadcaster.hasEventListeners());
+
+        //Just check that there are no NPEs
+        broadcaster.broadcastEvent(ev);
+    }
+
+    public void testEventProducer() throws Exception {
+        MyEventListener listener = new MyEventListener();
+
+        EventBroadcaster broadcaster = new DefaultEventBroadcaster();
+        broadcaster.addEventListener(listener);
+        assertTrue(broadcaster.hasEventListeners());
+        
+        
+        TestEventProducer producer = TestEventProducer.Provider.get(broadcaster);
+        producer.complain(this, "I'm tired", 23);
+        
+        Event ev = listener.event;
+        assertNotNull(ev);
+        assertEquals("org.apache.fop.events.TestEventProducer.complain",
+                listener.event.getEventID());
+        assertEquals(EventSeverity.WARN, listener.event.getSeverity());
+        assertEquals("I'm tired", ev.getParam("reason"));
+        assertEquals(new Integer(23), ev.getParam("blah"));
+        
+        broadcaster.removeEventListener(listener);
+        assertFalse(broadcaster.hasEventListeners());
+
+        //Just check that there are no NPEs
+        broadcaster.broadcastEvent(ev);
+    }
+    
+    private class MyEventListener implements EventListener {
+
+        private Event event;
+        
+        public void processEvent(Event event) {
+            if (this.event != null) {
+                fail("Multiple events received");
+            }
+            this.event = event;
+        }
+    }
+
+}
diff --git a/test/java/org/apache/fop/events/FOPTestEventModelFactory.java b/test/java/org/apache/fop/events/FOPTestEventModelFactory.java
new file mode 100644 (file)
index 0000000..796a9cf
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+import org.apache.fop.events.model.AbstractEventModelFactory;
+import org.apache.fop.events.model.EventModel;
+
+/**
+ * Factory for FOP's test event model.
+ */
+public class FOPTestEventModelFactory extends AbstractEventModelFactory {
+
+    private static final String EVENT_MODEL_FILENAME = "test-event-model.xml";
+
+    /** {@inheritDoc} */
+    public EventModel createEventModel() {
+        return loadModel(getClass(), EVENT_MODEL_FILENAME);
+    }
+
+}
diff --git a/test/java/org/apache/fop/events/TestEventProducer.java b/test/java/org/apache/fop/events/TestEventProducer.java
new file mode 100644 (file)
index 0000000..7dfba75
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.events;
+
+public interface TestEventProducer extends EventProducer {
+
+    /**
+     * Complain about something.
+     * @param source the event source
+     * @param reason the reason for the complaint
+     * @param blah the complaint
+     * @event.severity WARN
+     */
+    void complain(Object source, String reason, int blah);
+    
+    /**
+     * Express joy about something.
+     * @param source the event source
+     * @param what the cause for the joy
+     * @event.severity INFO
+     */
+    void enjoy(Object source, String what);
+    public class Provider {
+        
+        public static TestEventProducer get(EventBroadcaster broadcaster) {
+            return (TestEventProducer)broadcaster.getEventProducerFor(TestEventProducer.class);
+        }
+    }
+    
+}
diff --git a/test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java b/test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java
new file mode 100644 (file)
index 0000000..c0e0035
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.xml.sax.helpers.LocatorImpl;
+
+import org.apache.fop.events.model.EventSeverity;
+import org.apache.fop.util.text.AdvancedMessageFormat;
+
+/**
+ * Tests for EventFormatter.
+ */
+public class AdvancedMessageFormatTestCase extends TestCase {
+
+    public void testFormatting() throws Exception {
+        String msg;
+        AdvancedMessageFormat format;
+        
+        String pattern
+            = "Element \"{elementName}\" is missing[ required property \"{propertyName}\"]!";
+        format = new AdvancedMessageFormat(pattern);
+
+        Map params = new java.util.HashMap();
+        params.put("node", new Object());
+        params.put("elementName", "fo:external-graphic");
+        params.put("propertyName", "src");
+        
+        msg = format.format(params);
+        assertEquals("Element \"fo:external-graphic\" is missing required property \"src\"!", msg);
+        
+        params.remove("propertyName");
+        msg = format.format(params);
+        assertEquals("Element \"fo:external-graphic\" is missing!", msg);
+        
+        pattern
+            = "Testing \\{escaped \\[characters\\], now a normal field {elementName}!";
+        format = new AdvancedMessageFormat(pattern);
+        msg = format.format(params);
+        assertEquals("Testing {escaped [characters], now a normal field fo:external-graphic!", msg);
+
+        pattern = "Multi-conditional: [case1: {var1}|case2: {var2}|case3: {var3}]";
+        format = new AdvancedMessageFormat(pattern);
+        
+        params = new java.util.HashMap();
+        msg = format.format(params);
+        assertEquals("Multi-conditional: ", msg);
+        
+        params.put("var3", "value3");
+        msg = format.format(params);
+        assertEquals("Multi-conditional: case3: value3", msg);
+        params.put("var1", "value1");
+        msg = format.format(params);
+        assertEquals("Multi-conditional: case1: value1", msg);
+    }
+    
+    public void testObjectFormatting() throws Exception {
+        String msg;
+        AdvancedMessageFormat format;
+        
+        String pattern
+            = "Here's a Locator: {locator}";
+        format = new AdvancedMessageFormat(pattern);
+
+        Map params = new java.util.HashMap();
+        LocatorImpl loc = new LocatorImpl();
+        loc.setColumnNumber(7);
+        loc.setLineNumber(12);
+        params.put("locator", loc);
+        
+        msg = format.format(params);
+        assertEquals("Here\'s a Locator: 12:7", msg);
+    }
+    
+    public void testIfFormatting() throws Exception {
+        String msg;
+        AdvancedMessageFormat format;
+        
+        format = new AdvancedMessageFormat("You are{isBad,if, not} nice!");
+
+        Map params = new java.util.HashMap();
+
+        params.put("isBad", Boolean.FALSE);
+        msg = format.format(params);
+        assertEquals("You are nice!", msg);
+
+        params.put("isBad", Boolean.TRUE);
+        msg = format.format(params);
+        assertEquals("You are not nice!", msg);
+
+        format = new AdvancedMessageFormat("You are{isGood,if, very, not so} nice!");
+
+        params = new java.util.HashMap();
+
+        msg = format.format(params); //isGood is missing
+        assertEquals("You are not so nice!", msg);
+
+        params.put("isGood", Boolean.FALSE);
+        msg = format.format(params);
+        assertEquals("You are not so nice!", msg);
+
+        params.put("isGood", Boolean.TRUE);
+        msg = format.format(params);
+        assertEquals("You are very nice!", msg);
+
+        format = new AdvancedMessageFormat("You are{isGood,if, very\\, very} nice!");
+
+        params = new java.util.HashMap();
+
+        msg = format.format(params); //isGood is missing
+        assertEquals("You are nice!", msg);
+
+        params.put("isGood", Boolean.FALSE);
+        msg = format.format(params);
+        assertEquals("You are nice!", msg);
+
+        params.put("isGood", Boolean.TRUE);
+        msg = format.format(params);
+        assertEquals("You are very, very nice!", msg);
+    }
+    
+    public void testEqualsFormatting() throws Exception {
+        String msg;
+        AdvancedMessageFormat format;
+        
+        format = new AdvancedMessageFormat(
+                "Error{severity,equals,EventSeverity:FATAL,,\nSome explanation!}");
+
+        Map params = new java.util.HashMap();
+
+        params.put("severity", EventSeverity.FATAL);
+        msg = format.format(params);
+        assertEquals("Error", msg);
+
+        params.put("severity", EventSeverity.WARN);
+        msg = format.format(params);
+        assertEquals("Error\nSome explanation!", msg);
+    }
+    
+    public void testChoiceFormatting() throws Exception {
+        String msg;
+        AdvancedMessageFormat format;
+        
+        format = new AdvancedMessageFormat(
+                "You have {amount,choice,0#nothing|0<{amount} bucks|100<more than enough}.");
+
+        Map params = new java.util.HashMap();
+
+        params.put("amount", new Integer(0));
+        msg = format.format(params);
+        assertEquals("You have nothing.", msg);
+
+        params.put("amount", new Integer(7));
+        msg = format.format(params);
+        assertEquals("You have 7 bucks.", msg);
+
+        params.put("amount", new Integer(140));
+        msg = format.format(params);
+        assertEquals("You have more than enough.", msg);
+
+    }
+    
+}
diff --git a/test/java/org/apache/fop/util/XMLResourceBundleTestCase.java b/test/java/org/apache/fop/util/XMLResourceBundleTestCase.java
new file mode 100644 (file)
index 0000000..a1ced32
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for XMLResourceBundle.
+ */
+public class XMLResourceBundleTestCase extends TestCase {
+
+    public void testWithValidFile() throws Exception {
+        ResourceBundle bundle = XMLResourceBundle.getXMLBundle(
+                getClass().getName(), Locale.ENGLISH, getClass().getClassLoader());
+        ResourceBundle bundleDE = XMLResourceBundle.getXMLBundle(
+                getClass().getName(), Locale.GERMAN, getClass().getClassLoader());
+        
+        assertEquals("", bundle.getLocale().getLanguage());
+        assertEquals("de", bundleDE.getLocale().getLanguage());
+
+        assertEquals("Hello World!", bundle.getString("hello-world"));
+        assertEquals("Hallo Welt!", bundleDE.getString("hello-world"));
+
+        //Check fallback to English
+        assertEquals("Untranslatable", bundle.getString("untranslatable"));
+        assertEquals("Untranslatable", bundleDE.getString("untranslatable"));
+    }
+    
+    public void testWithInvalidFile() throws Exception {
+        try {
+            ResourceBundle bundle = XMLResourceBundle.getXMLBundle(
+                    "org.apache.fop.util.invalid-translation-file", getClass().getClassLoader());
+            fail("Expected exception");
+        } catch (MissingResourceException e) {
+            //expected
+        }
+    }
+    
+}
diff --git a/test/java/org/apache/fop/util/XMLResourceBundleTestCase.xml b/test/java/org/apache/fop/util/XMLResourceBundleTestCase.xml
new file mode 100644 (file)
index 0000000..5cdee49
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key="hello-world">Hello World!</message>
+  <message key="untranslatable">Untranslatable</message>
+</catalogue>
\ No newline at end of file
diff --git a/test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml b/test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml
new file mode 100644 (file)
index 0000000..44cf5a5
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="de">
+  <message key="hello-world">Hallo Welt!</message>
+</catalogue>
\ No newline at end of file
diff --git a/test/java/org/apache/fop/util/invalid-translation-file.xml b/test/java/org/apache/fop/util/invalid-translation-file.xml
new file mode 100644 (file)
index 0000000..09306b7
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<catalogue xml:lang="en">
+  <message key1="hello-world">Hello World!</message>
+  <something>blah</something>
+</catalogue>
\ No newline at end of file