This is a post from my original site, which was hosted by the former blog service of the University of Osnabrück. I have moved it to the new site for archiving. Pages linked in this article may no longer work today, and the blog comments under the article no longer exist. Opinions expressed in this article reflect the point of view of the time of publication and do not necessarily reflect my opinion today.
JasperReports is a library which can be used to fill reports from Java applications or just create simple PDFs. It allows you to not only use static output strings but also Groovy expressions. Sadly, this is restricted to simple expressions that result in a value and don't generate multiple class files at compile time.
For example, you could use the following expression to print different values depending if your document has more or less than 10 pages:
$V{PAGE_COUNT} < 10 ? "foo" : "bar"
But when you have to loop over values you'll face a problem as you have no possibility to define own methods. Even the usage of closures (Groovy) or anonymous inner classes (Java) is prohibited as Jasper expects every expression to result in one single class file.
Of course you could extend the class path or use Scriptlets for this, but this requires you to ship the compiled class together with the report library.
Using the power of groovy there is a way to work around this: compile your expression at runtime! The following code will calculate the faculty of the number of pages:
newGroovyClassLoader().parseClass('''
def static fac(x) {
def res = 1;
1.upto(x) {
res *= it
}
return res
}
''').fac($V{PAGE_COUNT})
The first line creates a new instance of the GroovyClassLoader, parses the code given in the following multiline string expression and executes the method "fac(x)" defined statically before.
To prove the power of this method the following code will embed a recursive listing of your home directory in your report:
newGroovyClassLoader().parseClass('''
def static list(path, prefix) {
String result = ""
path.listFiles().each() {
result += prefix + it.name + "\\n"
result += list(it, prefix + " ")
}
return result
}
''').list(newFile(System.getProperty("user.home")),"")
Remember that you have to double-escape backslashes as the first escape will be handles by the Jasper compiler.
Another possibility (and the reason why I researched at this topic) is that this method allows you to generate images at runtime and use the full power of the JFreeChart library used by Jasper itself.
Whenever you want to add a Table of Contents to your Jasper Report you have a problem, because there is no build-in function to do this. Most solutions suggest to derive an own Scriptlet class, use subreports or provide a custom data source.
Let me add another quick & dirty solution that requires no external resources, not even the Groovy library. It works with pure embedded Java code, at least as long you're ok with the very simplified layout of this solution, and do not require to have the ToC at the beginning of your report. And furthermore, it is pretty simple.
First, create a new parameter of type java.lang.StringBuilder:
This parameter called toc will be filled with the table of content during the rendering of the detail sections. You may add markup if you want to.
Next, on the summary page, print the content of this parameter. I'll use HTML formated strings, here:
<statictext><reportElementx="0"y="0"width="540"height="20"/><textElementverticalAlignment="Middle"><fontsize="14"isBold="true"isItalic="true"/></textElement><text><![CDATA[Table of Contents]]></staticText><textFieldisStretchWithOverflow="true"><reportElementx="28"y="30"width="471"height="20"/><textElementmarkup="html"><fontfontName="Monospaced"size="8"/></textElement><textFieldExpression><![CDATA[$P{toc}.toString()]]></textFieldExpression></textField>
Maybe you noticed that I use a monospace font here. This will be explained later.
Whereever you want to have a new entry (e.g. in the detail band or within a group header) add a new text field and put the following expression into it:
$P{toc}.append(
$F{section}.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">")+"<font color='#999999'>"/* Fill the string with up to 80 dots */+newString(newchar[80-Math.min($F{section}.length(),80)]).replaceAll(".",".")+"</font>"+ $V{PAGE_NUMBER}+"<br>")==null?"":""
This works since StringBuilder.append returns an object (itself). It won't be visible since the expression will result in an empty string in any case.
In this example, I want to use the field "section" as an index. Because the resulting string will be formatted using html markup we have to escape "<" as "<", ">" as ">" and "&" as "&". In the next lines, you see the reason why I decided to use a monospace font for the ToC: the section name should be followed by up to 80 grey dots and the page number.
Since you cannot use loops within JasperReport's expressions (at least not without Groovy) I used the trick to create a new String containing the required number of 0x0 characters and replace all characters (the first parameter of String.replaceAll is a regular expression) with dots.