aboutsummaryrefslogtreecommitdiffstats
path: root/fop-events
diff options
context:
space:
mode:
authorGlenn Adams <gadams@apache.org>2016-03-12 05:39:53 +0000
committerGlenn Adams <gadams@apache.org>2016-03-12 05:39:53 +0000
commit6a719897d6f98ba89aa08e2f97b2b801be066cbf (patch)
treebcce1bb7e9c6bde85e1167cff6218948f5be9f0a /fop-events
parent2184238c210288138b6703bd63ccb2c8c40300f0 (diff)
downloadxmlgraphics-fop-6a719897d6f98ba89aa08e2f97b2b801be066cbf.tar.gz
xmlgraphics-fop-6a719897d6f98ba89aa08e2f97b2b801be066cbf.zip
Configure fop-{sandbox,servlet,transcoder}, checkstyle, findbugs; fixups for ant targets.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/maven@1734671 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'fop-events')
-rw-r--r--fop-events/pom.xml147
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/CompositeEventListener.java65
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/DefaultEventBroadcaster.java183
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/Event.java190
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/EventBroadcaster.java61
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/EventExceptionManager.java102
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/EventFormatter.java189
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/EventListener.java38
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/EventProducer.java31
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/EventMethodModel.java198
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/EventModel.java146
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java143
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/EventProducerModel.java105
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/EventSeverity.java82
-rw-r--r--fop-events/src/main/java/org/apache/fop/events/model/package.html23
-rw-r--r--fop-events/src/main/java/org/apache/fop/tools/EventConventionException.java37
-rw-r--r--fop-events/src/main/java/org/apache/fop/tools/EventProducerCollector.java203
-rw-r--r--fop-events/src/main/java/org/apache/fop/tools/EventProducerCollectorTask.java287
-rw-r--r--fop-events/src/main/resources/org/apache/fop/tools/merge-translation.xsl55
-rw-r--r--fop-events/src/main/resources/org/apache/fop/tools/model2translation.xsl35
-rw-r--r--fop-events/src/test/java/org/apache/fop/events/BasicEventTestCase.java103
-rw-r--r--fop-events/src/test/java/org/apache/fop/events/TestEventProducer.java51
22 files changed, 2474 insertions, 0 deletions
diff --git a/fop-events/pom.xml b/fop-events/pom.xml
new file mode 100644
index 000000000..548d0adf5
--- /dev/null
+++ b/fop-events/pom.xml
@@ -0,0 +1,147 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.xmlgraphics</groupId>
+ <artifactId>fop-events</artifactId>
+ <name>Apache FOP Events</name>
+ <description>XML Graphics Format Object Processor Events</description>
+
+ <parent>
+ <groupId>org.apache.xmlgraphics</groupId>
+ <artifactId>fop-parent</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+
+ <dependencies>
+ <!-- fop deps -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>fop-util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- external deps -->
+ <dependency>
+ <groupId>com.thoughtworks.qdox</groupId>
+ <artifactId>qdox</artifactId>
+ <version>1.12</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons.io.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant</artifactId>
+ <version>1.8.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.xmlgraphics</groupId>
+ <artifactId>xmlgraphics-commons</artifactId>
+ <version>${xmlgraphics.commons.version}</version>
+ </dependency>
+ <!-- test-only deps -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- generate event collector models -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>${antrun.plugin.version}</version>
+ <executions>
+ <execution>
+ <id>codegen-test-events</id>
+ <phase>process-test-classes</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <taskdef name="collectEvents" classname="org.apache.fop.tools.EventProducerCollectorTask">
+ <classpath>
+ <path refid="maven.compile.classpath"/>
+ <path refid="maven.test.classpath"/>
+ </classpath>
+ </taskdef>
+ <collectEvents destdir="${project.build.testOutputDirectory}">
+ <fileset dir="${project.basedir}/src/test/java">
+ <include name="**/events/TestEventProducer.java"/>
+ </fileset>
+ </collectEvents>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- junit testing -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+ <!-- code analysis - checkstyle -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <configLocation>${project.baseUri}../fop-core/src/tools/resources/checkstyle/checkstyle.xml</configLocation>
+ <headerLocation>${project.baseUri}../fop-core/src/tools/resources/checkstyle/LICENSE.txt</headerLocation>
+ <includeResources>false</includeResources>
+ <includeTestResources>false</includeTestResources>
+ <linkXRef>false</linkXRef>
+ <logViolationsToConsole>true</logViolationsToConsole>
+ <suppressionsLocation>${project.baseUri}../fop-core/src/tools/resources/checkstyle/suppressions.xml</suppressionsLocation>
+ <violationSeverity>warning</violationSeverity>
+ </configuration>
+ </plugin>
+ <!-- code analysis - findbugs -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>${findbugs.plugin.version}</version>
+ <configuration>
+ <excludeFilterFile>../fop-core/src/tools/resources/findbugs/exclusions.xml</excludeFilterFile>
+ <effort>Max</effort>
+ <threshold>Low</threshold>
+ </configuration>
+ </plugin>
+ </plugins>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <directory>${basedir}/..</directory>
+ <includes>
+ <include>LICENSE</include>
+ <include>NOTICE</include>
+ </includes>
+ <targetPath>META-INF</targetPath>
+ </resource>
+ </resources>
+ <testResources>
+ <testResource>
+ <directory>src/test/resources</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </testResource>
+ </testResources>
+ </build>
+
+</project>
diff --git a/fop-events/src/main/java/org/apache/fop/events/CompositeEventListener.java b/fop-events/src/main/java/org/apache/fop/events/CompositeEventListener.java
new file mode 100644
index 000000000..c2bef322e
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/CompositeEventListener.java
@@ -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.events;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * EventListener implementation forwards events to possibly multiple other EventListeners.
+ */
+public class CompositeEventListener implements EventListener {
+
+ private List<EventListener> listeners = new ArrayList<EventListener>();
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Indicates whether any listeners have been registered with the broadcaster.
+ * @return true if listeners are present, false otherwise
+ */
+ public synchronized boolean hasEventListeners() {
+ return !listeners.isEmpty();
+ }
+
+ /** {@inheritDoc } */
+ public synchronized void processEvent(Event event) {
+ for (EventListener listener : listeners) {
+ listener.processEvent(event);
+ }
+ }
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/DefaultEventBroadcaster.java b/fop-events/src/main/java/org/apache/fop/events/DefaultEventBroadcaster.java
new file mode 100644
index 000000000..596a0a86b
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/DefaultEventBroadcaster.java
@@ -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.events;
+
+import java.io.InputStream;
+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 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.model.EventMethodModel;
+import org.apache.fop.events.model.EventModel;
+import org.apache.fop.events.model.EventModelParser;
+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();
+
+ /**
+ * Loads an event model and returns its instance.
+ * @param resourceBaseClass base class to use for loading resources
+ * @return the newly loaded event model.
+ */
+ private static EventModel loadModel(Class resourceBaseClass) {
+ String resourceName = "event-model.xml";
+ 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);
+ }
+ }
+
+ /**
+ * Adds a new {@link EventModel} to the list of registered event models.
+ * @param eventModel the event model instance
+ */
+ public static synchronized void addEventModel(EventModel eventModel) {
+ eventModels.add(eventModel);
+ }
+
+ private static synchronized 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;
+ }
+ }
+ EventModel model = loadModel(clazz);
+ addEventModel(model);
+ return model.getProducer(clazz);
+ }
+
+ /** {@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;
+ }
+
+ /**
+ * 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/fop-events/src/main/java/org/apache/fop/events/Event.java b/fop-events/src/main/java/org/apache/fop/events/Event.java
new file mode 100644
index 000000000..7c5759e6b
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/Event.java
@@ -0,0 +1,190 @@
+/*
+ * 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.Locale;
+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, a locale (for formatting event messages), and a map of
+ * name/value pairs.
+ */
+public class Event extends EventObject {
+
+ private static final long serialVersionUID = -1310594422868258083L;
+
+ private String eventGroupID;
+ private String eventKey;
+ private EventSeverity severity;
+ private Locale locale;
+ private Map<String, Object> params;
+
+ /**
+ * Creates a new Event using default locale.
+ * @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<String, Object> params)
+ {
+ this (source, eventID, severity, Locale.getDefault(), 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 locale to use when formatting event (or null, which means use default locale)
+ * @param params the event parameters (a map of name/value pairs)
+ */
+ public Event(Object source, String eventID, EventSeverity severity, Locale locale, Map<String, Object> params)
+ {
+ super(source);
+ int pos = eventID.lastIndexOf('.');
+ if (pos < 0 || pos == eventID.length() - 1) {
+ eventKey = eventID;
+ } else {
+ eventGroupID = eventID.substring(0, pos);
+ eventKey = eventID.substring(pos + 1);
+ }
+ setSeverity(severity);
+ this.locale = locale;
+ this.params = params;
+ }
+
+ /**
+ * Returns the event identifier.
+ * @return the event identifier
+ */
+ public String getEventID() {
+ if (eventGroupID == null) {
+ return eventKey;
+ } else {
+ return eventGroupID + '.' + eventKey;
+ }
+ }
+
+ /**
+ * Returns the event group identifier.
+ * @return the event group identifier (or null if there is no group identifier)
+ */
+ public String getEventGroupID() {
+ return eventGroupID;
+ }
+
+ /**
+ * Returns the event key.
+ * @return the event key
+ */
+ public String getEventKey() {
+ return eventKey;
+ }
+
+ /**
+ * 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 the locale.
+ * @return the locale
+ */
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ /**
+ * 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<String, Object> 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<String, Object> 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<String, Object>();
+ }
+ this.params.put(name, value);
+ return this;
+ }
+
+ /**
+ * Returns the accumulated parameter map.
+ * @return the accumulated parameter map
+ */
+ public Map<String, Object> build() {
+ return this.params;
+ }
+ }
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/EventBroadcaster.java b/fop-events/src/main/java/org/apache/fop/events/EventBroadcaster.java
new file mode 100644
index 000000000..57f30c9cf
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/EventBroadcaster.java
@@ -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
+ */
+ <T extends EventProducer> T getEventProducerFor(Class<T> clazz);
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/EventExceptionManager.java b/fop-events/src/main/java/org/apache/fop/events/EventExceptionManager.java
new file mode 100644
index 000000000..e9208fbc6
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/EventExceptionManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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 responsible for converting events into exceptions.
+ */
+public final class EventExceptionManager {
+
+ private EventExceptionManager() {
+ }
+
+ private static final Map<String, ExceptionFactory> EXCEPTION_FACTORIES
+ = new java.util.HashMap<String, ExceptionFactory>();
+
+ static {
+ Iterator<Object> iter;
+ iter = Service.providers(ExceptionFactory.class);
+ 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 = 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);
+ //Get original exception as cause if it is given as one of the parameters
+ Throwable t = null;
+ Iterator<Object> iter = event.getParams().values().iterator();
+ while (iter.hasNext()) {
+ Object o = iter.next();
+ if (o instanceof Throwable) {
+ t = (Throwable)o;
+ break;
+ }
+ }
+ if (t != null) {
+ throw new RuntimeException(msg, t);
+ } else {
+ 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<? extends Exception> getExceptionClass();
+ }
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/EventFormatter.java b/fop-events/src/main/java/org/apache/fop/events/EventFormatter.java
new file mode 100644
index 000000000..7f56c8396
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/EventFormatter.java
@@ -0,0 +1,189 @@
+/*
+ * 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 Log log = LogFactory.getLog(EventFormatter.class);
+
+ private EventFormatter() {
+ //utility class
+ }
+
+ private static ResourceBundle getBundle(String groupID, Locale locale) {
+ ResourceBundle bundle;
+ String baseName = (groupID != null) ? groupID : EventFormatter.class.getName();
+ try {
+ ClassLoader classLoader = EventFormatter.class.getClassLoader();
+ bundle = XMLResourceBundle.getXMLBundle(baseName, locale, classLoader);
+ } catch (MissingResourceException e) {
+ if (log.isTraceEnabled()) {
+ log.trace("No XMLResourceBundle for " + baseName + " available.");
+ }
+ bundle = null;
+ }
+ return bundle;
+ }
+
+ /**
+ * Formats an event using the default locale.
+ * @param event the event
+ * @return the formatted message
+ */
+ public static String format(Event event) {
+ return format(event, event.getLocale());
+ }
+
+ /**
+ * 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) {
+ return format(event, getBundle(event.getEventGroupID(), locale));
+ }
+
+ private static String format(Event event, ResourceBundle bundle) {
+ assert event != null;
+ String key = event.getEventKey();
+ String template;
+ if (bundle != null) {
+ template = bundle.getString(key);
+ } else {
+ template = "Missing bundle. Can't lookup event key: '" + key + "'.";
+ }
+ 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;
+ if (bundle != null) {
+ 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());
+ params.put("groupID", event.getEventGroupID());
+ params.put("locale", event.getLocale());
+ 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) {
+ String groupID = (String) params.get("groupID");
+ Locale locale = (Locale) params.get("locale");
+ ResourceBundle bundle = getBundle(groupID, locale);
+ if (bundle != null) {
+ sb.append(bundle.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/fop-events/src/main/java/org/apache/fop/events/EventListener.java b/fop-events/src/main/java/org/apache/fop/events/EventListener.java
new file mode 100644
index 000000000..fa1bb5beb
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/EventListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+// @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
+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 occurred. This way the client can configure the behaviour of
+ * the observed application.
+ * @param event the event
+ */
+ void processEvent(Event event);
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/EventProducer.java b/fop-events/src/main/java/org/apache/fop/events/EventProducer.java
new file mode 100644
index 000000000..88da771a4
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/EventProducer.java
@@ -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/fop-events/src/main/java/org/apache/fop/events/model/EventMethodModel.java b/fop-events/src/main/java/org/apache/fop/events/model/EventMethodModel.java
new file mode 100644
index 000000000..b9c62f8ab
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/EventMethodModel.java
@@ -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("", "name", "name", "CDATA", getMethodName());
+ atts.addAttribute("", "severity", "severity", "CDATA", getSeverity().getName());
+ if (getExceptionClass() != null) {
+ atts.addAttribute("", "exception", "exception", "CDATA", getExceptionClass());
+ }
+ String elName = "method";
+ handler.startElement("", elName, elName, atts);
+ Iterator iter = this.params.iterator();
+ while (iter.hasNext()) {
+ ((XMLizable)iter.next()).toSAX(handler);
+ }
+ handler.endElement("", 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("", "type", "type", "CDATA", getType().getName());
+ atts.addAttribute("", "name", "name", "CDATA", getName());
+ String elName = "parameter";
+ handler.startElement("", elName, elName, atts);
+ handler.endElement("", elName, elName);
+ }
+
+ }
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/model/EventModel.java b/fop-events/src/main/java/org/apache/fop/events/model/EventModel.java
new file mode 100644
index 000000000..9c11b79a3
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/EventModel.java
@@ -0,0 +1,146 @@
+/*
+ * 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.OutputStream;
+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.commons.io.IOUtils;
+
+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("", elName, elName, atts);
+ Iterator iter = getProducers();
+ while (iter.hasNext()) {
+ ((XMLizable)iter.next()).toSAX(handler);
+ }
+ handler.endElement("", elName, elName);
+ }
+
+ private void writeXMLizable(XMLizable object, File outputFile) throws IOException {
+ //These two approaches do not seem to work in all environments:
+ //Result res = new StreamResult(outputFile);
+ //Result res = new StreamResult(outputFile.toURI().toURL().toExternalForm());
+ //With an old Xalan version: file:/C:/.... --> file:\C:\.....
+ OutputStream out = new java.io.FileOutputStream(outputFile);
+ out = new java.io.BufferedOutputStream(out);
+ Result res = new StreamResult(out);
+
+ 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());
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+ }
+
+ /**
+ * 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/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java
new file mode 100644
index 000000000..5441d1c4f
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java
@@ -0,0 +1,143 @@
+/*
+ * 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 final class EventModelParser {
+
+ private EventModelParser() {
+ }
+
+ /** Logger instance */
+ private static final 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/fop-events/src/main/java/org/apache/fop/events/model/EventProducerModel.java b/fop-events/src/main/java/org/apache/fop/events/model/EventProducerModel.java
new file mode 100644
index 000000000..b0d334e99
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/EventProducerModel.java
@@ -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("", "name", "name", "CDATA", getInterfaceName());
+ String elName = "producer";
+ handler.startElement("", elName, elName, atts);
+ Iterator iter = getMethods();
+ while (iter.hasNext()) {
+ ((XMLizable)iter.next()).toSAX(handler);
+ }
+ handler.endElement("", elName, elName);
+ }
+
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/model/EventSeverity.java b/fop-events/src/main/java/org/apache/fop/events/model/EventSeverity.java
new file mode 100644
index 000000000..c0d886154
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/EventSeverity.java
@@ -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;
+ }
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/events/model/package.html b/fop-events/src/main/java/org/apache/fop/events/model/package.html
new file mode 100644
index 000000000..4d915721b
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/events/model/package.html
@@ -0,0 +1,23 @@
+<!--
+ 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$ -->
+<HTML>
+<TITLE>org.apache.fop.events.model Package</TITLE>
+<BODY>
+<P>Model classes for the event subsystem.</P>
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/fop-events/src/main/java/org/apache/fop/tools/EventConventionException.java b/fop-events/src/main/java/org/apache/fop/tools/EventConventionException.java
new file mode 100644
index 000000000..363850b95
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/tools/EventConventionException.java
@@ -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.tools;
+
+/**
+ * This exception is used to indicate a violation of the conventions for event producers.
+ */
+public class EventConventionException extends Exception {
+
+ private static final long serialVersionUID = 117244726033986628L;
+
+ /**
+ * Creates a new EventConventionException
+ * @param message the error message
+ */
+ public EventConventionException(String message) {
+ super(message);
+ }
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollector.java b/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollector.java
new file mode 100644
index 000000000..7151f5a9c
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollector.java
@@ -0,0 +1,203 @@
+/*
+ * 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.List;
+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.
+ */
+class EventProducerCollector {
+
+ private static final String CLASSNAME_EVENT_PRODUCER = EventProducer.class.getName();
+ private static final Map<String, Class<?>> PRIMITIVE_MAP;
+
+ static {
+ Map<String, Class<?>> m = new java.util.HashMap<String, Class<?>>();
+ 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 List<EventModel> models = new java.util.ArrayList<EventModel>();
+
+ /**
+ * Creates a new EventProducerCollector.
+ */
+ EventProducerCollector() {
+ this.tagFactory = createDocletTagFactory();
+ }
+
+ /**
+ * Creates the {@link DocletTagFactory} to be used by the collector.
+ * @return the doclet tag factory
+ */
+ protected DocletTagFactory createDocletTagFactory() {
+ return new DefaultDocletTagFactory();
+ }
+
+ /**
+ * Scans a file and processes it if it extends the {@link EventProducer} interface.
+ * @param src the source file (a Java source file)
+ * @return true if the file contained an EventProducer interface
+ * @throws IOException if an I/O error occurs
+ * @throws EventConventionException if the EventProducer conventions are violated
+ * @throws ClassNotFoundException if a required class cannot be found
+ */
+ public boolean scanFile(File src)
+ throws IOException, EventConventionException, ClassNotFoundException {
+ JavaDocBuilder builder = new JavaDocBuilder(this.tagFactory);
+ builder.addSource(src);
+ JavaClass[] classes = builder.getClasses();
+ boolean eventProducerFound = false;
+ for (int i = 0, c = classes.length; i < c; i++) {
+ JavaClass clazz = classes[i];
+ if (clazz.isInterface() && implementsInterface(clazz, CLASSNAME_EVENT_PRODUCER)) {
+ processEventProducerInterface(clazz);
+ eventProducerFound = true;
+ }
+ }
+ return eventProducerFound;
+ }
+
+ 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
+ * @throws EventConventionException if the event producer conventions are violated
+ * @throws ClassNotFoundException if a required class cannot be found
+ */
+ protected void processEventProducerInterface(JavaClass clazz)
+ 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);
+ }
+ EventModel model = new EventModel();
+ model.addProducer(prodMeta);
+ models.add(model);
+ }
+
+ private EventMethodModel createMethodModel(JavaMethod method)
+ throws EventConventionException, ClassNotFoundException {
+ JavaClass clazz = method.getParentClass();
+ //Check EventProducer conventions
+ if (!method.getReturnType().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 = 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;
+ }
+
+ /**
+ * Returns the event model that has been accumulated.
+ * @return the event model.
+ */
+ public List<EventModel> getModels() {
+ return this.models;
+ }
+
+}
diff --git a/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollectorTask.java b/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollectorTask.java
new file mode 100644
index 000000000..85c10f09a
--- /dev/null
+++ b/fop-events/src/main/java/org/apache/fop/tools/EventProducerCollectorTask.java
@@ -0,0 +1,287 @@
+/*
+ * 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.io.OutputStream;
+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.commons.io.IOUtils;
+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;
+
+import org.apache.fop.events.model.EventModel;
+import org.apache.fop.events.model.EventProducerModel;
+
+/**
+ * Ant task which inspects a file set for Java interfaces which extend the
+ * {@link org.apache.fop.events.EventProducer} interface. For all such interfaces an event model
+ * file and a translation file for the human-readable messages generated by the events is
+ * created and/or updated.
+ */
+public class EventProducerCollectorTask extends Task {
+
+ private List<FileSet> filesets = new java.util.ArrayList<FileSet>();
+ private File destDir;
+ private File translationFile;
+
+ /** {@inheritDoc} */
+ public void execute() throws BuildException {
+ try {
+ EventProducerCollector collector = new EventProducerCollector();
+ long lastModified = processFileSets(collector);
+ for (Iterator iter = collector.getModels().iterator(); iter.hasNext();) {
+ EventModel model = (EventModel) iter.next();
+ File parentDir = getParentDir(model);
+ if (!parentDir.exists() && !parentDir.mkdirs()) {
+ throw new BuildException(
+ "Could not create target directory for event model file: " + parentDir);
+ }
+ File modelFile = new File(parentDir, "event-model.xml");
+ if (!modelFile.exists() || lastModified > modelFile.lastModified()) {
+ model.saveToXML(modelFile);
+ log("Event model written to " + modelFile);
+ }
+ if (getTranslationFile() != null) {
+ // TODO Remove translation file creation facility?
+ if (!getTranslationFile().exists()
+ || lastModified > getTranslationFile().lastModified()) {
+ updateTranslationFile(modelFile);
+ }
+ }
+ }
+ } 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";
+
+ private File getParentDir(EventModel model) {
+ Iterator iter = model.getProducers();
+ assert iter.hasNext();
+ EventProducerModel producer = (EventProducerModel) iter.next();
+ assert !iter.hasNext();
+ String interfaceName = producer.getInterfaceName();
+ int startLocalName = interfaceName.lastIndexOf(".");
+ if (startLocalName < 0) {
+ return destDir;
+ } else {
+ String dirname = interfaceName.substring(0, startLocalName);
+ dirname = dirname.replace('.', File.separatorChar);
+ return new File(destDir, dirname);
+ }
+ }
+
+ /**
+ * Updates the translation file with new entries for newly found event producer methods.
+ * @param modelFile the model file to use
+ * @throws IOException if an I/O error occurs
+ */
+ protected void updateTranslationFile(File modelFile) throws IOException {
+ try {
+ boolean resultExists = getTranslationFile().exists();
+ SAXTransformerFactory tFactory
+ = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+ //Generate fresh generated translation file as template
+ Source src = new StreamSource(modelFile.toURI().toURL().toExternalForm());
+ 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().toURI().toURL().toExternalForm());
+ 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);
+
+ //The following triggers a bug in older Xalan versions
+ //Result res = new StreamResult(getTranslationFile());
+ OutputStream out = new java.io.FileOutputStream(getTranslationFile());
+ out = new java.io.BufferedOutputStream(out);
+ Result res = new StreamResult(out);
+ try {
+ 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());
+ }
+ } finally {
+ IOUtils.closeQuietly(out);
+ }
+ } catch (TransformerException te) {
+ throw new IOException(te.getMessage());
+ }
+ }
+
+ /**
+ * Processes the file sets defined for the task.
+ * @param collector the collector to use for collecting the event producers
+ * @return the time of the latest modification of any of the files inspected
+ * @throws IOException if an I/O error occurs
+ * @throws EventConventionException if the EventProducer conventions are violated
+ * @throws ClassNotFoundException if a required class cannot be found
+ */
+ protected long processFileSets(EventProducerCollector collector)
+ throws IOException, EventConventionException, ClassNotFoundException {
+ long lastModified = 0;
+ Iterator<FileSet> 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);
+ boolean eventProducerFound = collector.scanFile(src);
+ if (eventProducerFound) {
+ lastModified = Math.max(lastModified, src.lastModified());
+ }
+ }
+ }
+ return lastModified;
+ }
+
+ /**
+ * Adds a file set.
+ * @param set the file set
+ */
+ public void addFileset(FileSet set) {
+ filesets.add(set);
+ }
+
+ /**
+ * Sets the destination directory for the event models.
+ *
+ * @param destDir the destination directory
+ */
+ public void setDestDir(File destDir) {
+ if (!destDir.isDirectory()) {
+ throw new IllegalArgumentException("destDir must be a directory");
+ }
+ this.destDir = destDir;
+ }
+
+ /**
+ * Sets the translation file for the event producer methods.
+ * @param f the translation file
+ */
+ public void setTranslationFile(File f) {
+ this.translationFile = f;
+ }
+
+ /**
+ * Returns the translation file for the event producer methods.
+ * @return the translation file
+ */
+ public File getTranslationFile() {
+ return this.translationFile;
+ }
+
+ /**
+ * Command-line interface for testing purposes.
+ * @param args the command-line arguments
+ */
+ 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.setTranslationFile(new File("out1.xml"));
+ generator.execute();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/fop-events/src/main/resources/org/apache/fop/tools/merge-translation.xsl b/fop-events/src/main/resources/org/apache/fop/tools/merge-translation.xsl
new file mode 100644
index 000000000..d15d22fd3
--- /dev/null
+++ b/fop-events/src/main/resources/org/apache/fop/tools/merge-translation.xsl
@@ -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/fop-events/src/main/resources/org/apache/fop/tools/model2translation.xsl b/fop-events/src/main/resources/org/apache/fop/tools/model2translation.xsl
new file mode 100644
index 000000000..a1cf404ee
--- /dev/null
+++ b/fop-events/src/main/resources/org/apache/fop/tools/model2translation.xsl
@@ -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>
diff --git a/fop-events/src/test/java/org/apache/fop/events/BasicEventTestCase.java b/fop-events/src/test/java/org/apache/fop/events/BasicEventTestCase.java
new file mode 100644
index 000000000..39e3fec98
--- /dev/null
+++ b/fop-events/src/test/java/org/apache/fop/events/BasicEventTestCase.java
@@ -0,0 +1,103 @@
+/*
+ * 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.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.fop.events.model.EventSeverity;
+
+public class BasicEventTestCase {
+
+ @Test
+ 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);
+ }
+
+ @Test
+ 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/fop-events/src/test/java/org/apache/fop/events/TestEventProducer.java b/fop-events/src/test/java/org/apache/fop/events/TestEventProducer.java
new file mode 100644
index 000000000..ff19c7ea6
--- /dev/null
+++ b/fop-events/src/test/java/org/apache/fop/events/TestEventProducer.java
@@ -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;
+
+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 static final class Provider {
+
+ private Provider() {
+ }
+
+ public static TestEventProducer get(EventBroadcaster broadcaster) {
+ return (TestEventProducer)broadcaster.getEventProducerFor(TestEventProducer.class);
+ }
+ }
+
+}