Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

Note: This document needs to be updated to fully match the function system of 0.8.9/0.8.11.

Functions

JFreeReport supports two kinds of userdefined calculations: Functions are statefull calculations, when the report proceedes they change their state, they sum up, calculate averages, count etc. Statefull means in that case, given you feed the same event multiple times into the function, it is not guaranteed that you get the same result.

Expressions in contrast do not maintain a state, they calculate a value or change the report-elements depending on the current Event feed to them. If you feed the same Event into the Expression multiple times, you will always get the same result.

If possible try to prefer Expressions over functions, as expressions are easier to write and comsume less resources.

Using Functions and Expressions

...

Functions and expressions

A rough guide to Functions and Expressions

Both Expressions and Functions are parametrized as JavaBeans by creating getter and setter methods. Both use the getValue() function to return the computed value and both should create a new fresh instance that does not share any modifiable object whenever the getInstance() method is called. If possible try to prefer Expressions over functions, as expressions are easier to write and consume less resources. Formulas added to a report, are added as "Formula-Expressions" and therefore are stateless computations.

Expressions

Expressions are stateless, that means every call to the expression's getValue() is only dependent on the Expression's parameters and the values read from the datarow. The order in which expressions are called is not guaranteed, so it can be that the expression is called for the first row, then the 10th row and then for any other row. Due to their statelessness, expressions are cheap, as we do not have to clone them all the time to save their state.

Functions

Functions are statefull computations. Functions receive events (@see the Function interface) in a defined order, and maintain and update their state on every event. As the reporting engine iterates over the data multiple time, Functions get called several time. Each iteration has a processing-level, which describes the global state of the report-processing. Processing-levels greater or equal zero are function-processing levels, where functions compute the values. Level -1 (LayoutProcess.LEVEL_COLLECT) is reserved for layout preparations and Level -2 (LayoutProcess.LEVEL_PAGINATE) computes the layout.

The order of the function processing can be controlled via the Dependency-Level property of the Function. Functions with a higher dependency level are executed before functions with a lower dependency level. Functions will not be executed until the processing reached their respective level so that they do not see invalid results.

Functions that perform running computations (like the ItemSumFunction) are easy, as they can rely on the standard clone() functionality for the state management and simply recompute everything from scratch on each processing level.

Functions that compute global values (like the TotalGroupSumFunction), where function results are available before all data for the processing level has been processed are more complicated. These functions compute the running value in their defined processing level (@see FunctionUtilities#isDefinedPrepareRunLevel(..)) and store the result in a internal storage (usually a Map). On any later processing run, they simply recall the stored value to make it possible to print the value in report- or group-headers.

The event-order functions receive is well-defined. When saving report-states, the reporting engine will clone the function. This way, for a single function-instance, the event-flow always appears linear. The event-order is documented in the Wiki: http://wiki.pentaho.com/display/Reporting/JFR8States

You can monitor the event-flow by adding the EventMonitorFunction to your report. The function computes nothing but it logs all incoming report-processing events. Usually just looking at that log output makes the processing system a lot more understandable.

Using Functions and Expressions

Functions and expression will be configured using a set of properties ? these . Expression-properties are private to the function expression instance and will not be shared with other functions. Which properties need to be defined is dependend of the used implementation.Expressions and functions must have assigned a name, the The properties are always design-time properties and cannot change during the report-processing.

Expressions that compute a value that should be reused in a field or other expression must have a name assigned. The name must be unique within the datarow to access the functions computation resultthe current report and must not be the same as the name of any other expression, parameter or column from the data-source within the same report.

Functions are added to the report before the report processing starts. If you use the API, you will use the JfreeReport.addFunctionMasterReport#addFunction() or JfreeReport.addExpressionMasterReport#addExpression() methods to add the function objects to the report. When using the xml, you will specifiy the functions in the ?functions? tag of the report definition file. The functions declaration is equal for both report definition formats.Functions can be added or configured dynamically by using a Report-Preprocessor.

Function dependencies

Functions may query other functions and user these foreign results for their own computations.

The order in which functions get informed of events is undefined for functions of the same dependency level, but it It is guaranteed, that all functions are executed in the order of their
dependecy dependency level. Functions with an higher dependency are executed before any function with an lower dependency. The execution order of functions with the same dependecy dependency level is undefined.

Lets have a look at a small example:

Assume we have defined three functions named A, B and C. Furthermore we define, that function A, B and C are numerical functions and that C computes the sum of A and B.

When all functions have Functions within the same dependency level , the order of the event calls is undefined, so that the function A or B may contain invalid values when queried from C. So resolve this relationship, we have to declare that function C depends on A and B. This can be done by setting the dependency level of A and B higher than the dependency level of C.

When C has a dependency level of '0', we will have to define our functions A and B to have the dependency level of '1' or higher to make sure that the computation is valid.

When are executed in the order they have been added to the report.

When using the API, the dependency level is read by "getDependencyLevel()" and can be set with "setDependencyLevel(int)". In the XML definitions, the dependency can be defined using the attribute ?deplevel?.

By default all user functions have the dependency of "0", the lowest possible level. System functions like the page layouter have the a dependecy level of -1 or lower to make sure that all user defined expressions are evaluated before we try to create content. You cannot set the dependency of normal functions or expressions lower than '0'.

Dependencies between Expressions are not resolved automaticly. If this resolving results in an circular reference, an exception is thrown an the expressions will not be evaluated.automatically. The order of the expressions on the report determines the evaluation order in the report. A expression that references an other expression's value that is computed after the current expression will behave non-deterministic, as it will receive an old and invalid value from the datarow.

Implementing Expressions and Functions

Writing the Java Classes

Expressions are simple and easily explained: Expression have the method

Code Block

Object getValue() 

To minimize your implementation effort and to shield you against future changes to any management properties, we recommend that you use "AbstractExpression" as base-class for expressions.

which is called when the expression is queried. All expression have to override this method to perform the calculation and to return the value.

Functions are slightly more complex:
Functions have several notification methods, where the system informs the function that a new event is processed.

  • public void pageStarted (ReportEvent event);
  • public void reportStarted (ReportEvent event);
  • public void groupStarted (ReportEvent event);
  • public void itemsStarted (ReportEvent event);
  • public void itemsAdvanced (ReportEvent event);
  • public void itemsFinished (ReportEvent event);
  • public void groupFinished (ReportEvent event);
  • public void reportFinished (ReportEvent event);
  • public void pageFinished (ReportEvent event);

By using the Event-object, you have access to the ReportState. The state can be used for getting the current group, current item and for gaining access to the dataRow. In case you want to manipulate the ReportElements, you also have access to the MasterReport object.

To minimize your implementation effort and to shield you against future changes to any management properties, we recommend that you use "AbstractFunction" as base-class for your functions.

As with every function, a single function can return one value, something mathematicians express like y = f(x) where x is the data from your report processing.

The expression value is returned by the "getValue()" function. You can return any Object, but make sure, that this object does not get modified anymore after it was returned to the caller. (A simple way to obey to this rule is to create a new object whenever the computed object changes).

The expression implementation is loaded via its default constructor by the report parser, all function implementations that are expected to be used from within the xml-definitions must define a public default constructor. The functions implementation is defined by specifying the "class" attribute of the "function" tag and is loaded by using the default class loader. Your expression implementation must be in the classloaders classpath. To see the expression in the report-designer and to ensure a correct serialization and deserialization to and from XML, make sure you have metadata declared for your expression and injected it into the boot-process.

Declaring an extension module and providing the Meta-data

Todo

The life cycle of functions

...

A function receives the following events in the given order:

  • ReportInitialized event

...

  • (

...

  • pageStarted event)
  • prepareEvent (

...

  • ReportStarted)
  • ReportStarted event
  • prepareEvent (GroupStarted)
  • GroupStarted event
  • prepareEvent (ItemsStarted)
  • ItemsStarted event
  • prepareEvent (ItemsAdvanced)
  • ItemsAdvanced event
  • prepareEvent (ItemsFinished)
  • ItemsFinished event
  • prepareEvent (GroupFinished)
  • GroupFinished event
  • prepareEvent (ReportFinished)
  • ReportFinished event
  • prepareEvent (ReportDone)
  • ReportDone event
  • (PageFinished event)

...

The prepare events are used to inform the listeners, that the next state is going to be processed. When this event is thrown, no change was done by the state yet. The main purpose of these events is to help the function to clean up its internal states before the new state is beeing processed. The PageLayouter functions starts a new page and print eventually contained spooled bands after a pagebreak was done, when this event is received.

...

If you want to see, which and when events are fired during the report processing, you may want to include the EventMonitorFunction into your report. This function does not return any values, but prints a lot of log messages whenever an event is sent to that function.

Implementing the classes

Expressions are simple and easily explained: Expression have the method

Object getValue() which is called when the expression is queried. All expression have to override this method to perform the calculation and to return the value.

Functions are more complex:
Functions have several notification methods, where the system informs the function that a new event is processed.

  • public void pageStarted (ReportEvent event);
  • public void reportStarted (ReportEvent event);
  • public void groupStarted (ReportEvent event);
  • public void itemsStarted (ReportEvent event);
  • public void itemsAdvanced (ReportEvent event);
  • public void itemsFinished (ReportEvent event);
  • public void groupFinished (ReportEvent event);
  • public void reportFinished (ReportEvent event);
  • public void pageFinished (ReportEvent event);

By using the Event-object, you have access to the ReportState. The state can be used for getting the current group, current item and for gaining access to the dataRow. In case you want to manipulate the ReportElements, you also have access to the JFreeReport object.

Using the functions

The function implementation is loaded via its default constructor by the report parser, all function implementations which should be used from within the xml-definitions must define a public default constructor. The functions implementation is defined by specifiying the "class" attribute of the "function" tag and is loaded by using the default class loader. Your function implementation must be in the classloaders classpath.

As with every function, a single function can return one value, something mathematicans express like y = f(error) where x is the data from your report processing.

A function is called several times, for every state change (whenever the report advances) one of the ReportListener methods are called (reportStarted, groupStarted etc). Expressions do not receive events, they just perform their computations based on the current state.

The functions value is returned by the "getValue()" function. You can return any Object, but make sure, that this object does not get modified anymore after it was returned to the caller. (A simple way to obey to this rule is to create a new object whenever the computed object changes).

Functions may reuse results from other functions by using and querying the DataRow object supplied in the report state or the function.

The order of the functions and the order for receiving events is undefined for functions of the same dependency level. So if you define a function which reads values from an other function, it is wise to define the dependencies for these functions.

Defining dependencies is easy: A function with an higher dependency is called before any function with a lower dependency. By default all user functions have the dependency of "0", the lowest possible level. The dependency is defined by specifying the "deplevel" attribute of the function or expression tag.

When using the API, the dependency level is read by "getDependencyLevel()" and can be set with "setDependencyLevel(int)".

For the example above, the caller function would have the dependency level of '0' and the called function the level of '1' or higher.