|12.1||A case study: generating Java business equations|
|12.2||A case study: the report logic and interface generator|
Business logic is the area of an application with the most edge cases and custom behaviors. For this reason, you may think that code generation cannot be used to build business logic. And it’s true that you probably won’t be able to generate all of the business logic code, but in this chapter you’ll see some creative uses of code generation that should help you automate portions of it, which will speed your development greatly.
We’ve provided some examples of generators that can be used to make building portions of an application’s quality business logic code easier. We start with a generator that aids in building the equation code that is commonplace in scientific and business applications but that can be difficult to build by hand in Java.
If you can’t generate all of the business logic code, you can at least lessen the burden by building some of the tougher parts. Take, for example, writing equations for business applications in Java. Java’s Double class can hold a lot of precision for numeric values, but if you want to avoid overflows on really large sums you should use the BigDecimal class to hold your values.
The problem is that Java doesn’t support operator overloading. To add two BigDecimal objects together, you need to use the add method on BigDecimal as shown here:
BigDecimal v1 = new BigDecimal( 1.0 ); BigDecimal v2 = new BigDecimal( 2.0 ); BigDecimal v3 = new BigDecimal( 0 ); v1.add( v2 );
This simple code adds 1 plus 2 using BigDecimal. It’s not hard to imagine how complex the Java code could become for large or complex equations.
The generator should allow you to specify an equation using standard postfix notation (e.g., a = b + c) and then create the equation implementation for you. Listing 12.1 shows the example input file for the generator.
The output file for the generator is also the input file. Listing 12.2 shows the output from the generator.
The comment is preserved for two reasons. First, this allows engineers to see the equation at the high level. Second, the generator finds these comments the next time it runs and uses them as prompts to rebuild the equation.
Before describing the architecture of the generator, it’s important to understand what the generator handles as opposed to what other system components (or engineers) handle. The generator has one main responsibility: replacing the equation specification with implementation code for the equation.
The generator leaves these tasks to other systems (or people):
- Documenting the equation.
- Ensuring that the input or output variable types are correct.
Now that you understand the role of the generator within the larger system, you can define the architecture of the generator itself.
The generator reads a Java file as input. It then takes the text and augments the equation comments with equation implementations. Next, it backs up the original file and replaces it with the new file that contains the equation implementations. Figure 12.1 shows the block architecture for the mixed-code generator that builds the BigDecimal equations.
Figure 12.1. A generator that takes equations embedded in Java code comments and implements them with BigDecimal classes
Now let’s look at the steps this generator will go through as it runs.
Here is the high-level process flow for the equation generator:
- Reads in the Java file.
- Finds each equation comment and follows these steps:
- Tokenizes the equation.
- Converts the tokens to a binary tree representation of the equation.
- Walks the tree and creates Java code that builds variables and invokes methods to implement each node.
- Rebuilds the comment with the equation in the middle.
- Backs up the original file.
- Replaces the original file with the new text.
Now you’re ready to start building the actual code for the generator.
Listing 12.3 contains the Ruby code for the BigDecimal equation generation.
- vindex is a global that stores the ordinal number for the next temporary variable. Each time you create a new temporary BigDecimal object, you bump this value so that there is no possibility of a variable name collision.
- When you parse the equation into a tree, it will consist of EqNode objects. Each of these nodes is either a value or an operand. Variable objects represent values and operand objects represent operands (e.g., +, -, *, /). Both derive from EqNode. Variable is a specialization of EqNode. It doesn’t define any new functionality, but the equation parser uses the type to decide what to do with the node.
- TokenStream represents a set of tokens that make up an equation. For more about tokenizing, refer to the language parser toolkit documentation in chapter 3. This TokenStream class derives from the standard Ruby Array and adds helper methods for adding literals (variable names and constants) and operands.
- build_tokens reads an equation and tokenizes it.
- relate_tokens turns a flat array of tokens into a tree of equation nodes.
- new_equate_command creates Java to build a BigDecimal with a constant value.
- final_equate_command creates Java to assign the output of the equation to the local variable that is supposed to hold the output.
- new_op_command builds Java to implement plus, minus, multiply, or divide operations, or to call a method.
- create_commands walks the hierarchy and uses the Java building functions to create the Java that implements the equation.
- parse_equation joins together the text parsing and the Java creation. It takes the string of the equation and returns a string of Java that implements the equation.
- process_java_file reads the Java text file, reads the comments, and replaces them with implementation. It then backs up and replaces the original file.
To test the equation generator, use the system test (see appendix B), which compares the current output of the generator against a set of known goods. Here is the definition file for the generator:
<ut kgdir="kg"> <test cmd="ruby bdgen.rb examples/Test1.java" out="examples/Test1.java" /> <test cmd="ruby bdgen.rb examples/Test2.java" out="examples/Test2.java" /> <test cmd="ruby bdgen.rb examples/Test3.java" out="examples/Test3.java" /> <test cmd="ruby bdgen.rb examples/Test4.java" out="examples/Test4.java" /> </ut>
Next we’ll look at a type of generator that automates the building of reports.
Chapter 10, “Creating database access generators,” covered generation for the data entry and editing portions of an application. A major portion of any database application consists of the reports the system generates. In this section, we explain how you can apply code generation to building report generation.
Efficient reporting means using block requests of the database by creating custom queries that sum and group the data. The reporting system back end handles querying the database and getting the data into a form suitable for easy display. All of the math for the report must be done on the back end. The reporting system front end formats the output for the presentation medium.
This factoring of the business logic in the report back end from the display on the front end means that the report back end can be reused by any number of front-end mechanisms. These mechanisms could include:
- A batch report email sender
- A notification system based on the reporting values
- The exporting of report values through SOAP or XML-RPC
- The display of the report through multiple output mediums (e.g., HTML or client/server)
To demonstrate how a generator can be applied to this problem, let’s pick a Java web application as our target architecture. On the back end, we use custom POJOs (plain old Java objects) to query the database through JDBC. On the front end, we use JSP to build HTML from the report results.
The report generator is a key component of any business system. It’s also a component that frequently will have responsibilities added to its domain over time, which can cause it to become obfuscated. To avoid that feature creep, you should specify in clear terms what the system is responsible for and what it is not. Because the generator builds the reports, its responsibilities within the system are similar to the reporting engine itself. Let’s look at the specific responsibilities of this generator:
- Translating abstract requirements of fields and sorting them into a report.
- Creating the reporting layer of the application, which includes:
- The low-level reporting API that reads data from the database and massages it into a form appropriate for presentation.
- The presentation layer for each report.
- Technical documentation for the report.
- Rudimentary end-user documentation for the report.
The generator is not responsible for creating:
- Ad-hoc reports.
- Query criteria other than the criteria defined by the report.
- Design documentation for the reports built by the system.
With this in mind, you can now define the architecture of the generator.
The generator takes a report description and an interface description and, using a set of templates, builds the JSP pages and POJO objects. Custom code can also be input to alter the behavior of the back-end report builder. Figure 12.2 shows the block architecture for the tier generator.
- Arguments for the report query
- SQL for the query
- All of the fields returned from the query and their types
- The data to be grouped, sorted, and summed
- References to the custom code that handle various custom field behaviors
The interface description includes:
- Formatting for each of the fields of the display
- Arguments for the report, the locations from where those arguments came, and the controls that should be used to show the value of the argument
Report logic and interface generator
This section shows a list of processing steps for the generator. They are just a starting point; your report system generator will have custom requirements that alter this design.
- Reads the report description and stores it.
- Reads the interface description and stores it.
- Populates any default values in the interface description with the values from the report description.
- Creates a buffer to hold the field processing methods.
- For each field, follows these steps:
- If the field has custom code, uses the custom code template to build the field logic; otherwise, uses the standard field template to build the field logic.
- Uses the query template to build the JDBC access code.
- Merges the field code and the query code into a single POJO using a container template.
- Stores the output of the template as the report POJO.
- Invokes the interface template to build the JSP report page. This report page uses the POJO object to query the data.
- Stores the output of the template as the report JSP.
An alternative to building a single generator is to build two generators that cascade. The first builds the report logic and the second builds the interface. Figure 12.3 shows the block architecture diagram for two cascading generators.
Figure 12.3. An alternative take on a generation structure that builds reporting code for a web application
The report logic generator uses the report description to build the POJO classes. In addition, the generator produces a report interface specification that feeds into the interface generator. The interface generator uses the interface description in combination with the report interface specification to build JSPs that provide an interface for the reports.
This approach is preferred because you can reuse both the report generator and the interface generator. The report generator can be used to create customized report queries, which are used outside a user interface. You can reuse the interface generator to display data from any service that produces a report-style interface.
Tools that build report generators include the following:
- DataVision is an open source reporting tool (http://datavision.sourceforge.net/).
- JRPT is a Java report engine generator (http://sourceforge.net/projects/jrpt/).
Business logic layers are usually hand coded, but that doesn’t mean you can’t find ways to use generation. All it takes is a little creativity and you should be able to apply code generation to speed your development.
The next chapter provides smaller code generation solutions for a number of common software engineering problems.