资源说明:Design by Contract for Java
# README for Contract4J5: V0.9.0.0#
**Design-by-Contract® for Java Using Java 5 Annotations**
Dean Wampler
[polyglotprogramming.com/contract4j](http://polyglotprogramming.com/contract4j)
A project of [Aspect Research Associates](http://aspectresearchassociates.com)
## Contents
* Copyright
* Where to Get Contract4J5
* Naming and Versioning
* Distribution Manifest
* What Is "Contract4J5"?
* What is Design by Contract?
* Design by Contract and Aspect-Oriented Programming
* How Does Contract4J5 Support Design by Contract?
* Inheritance Behavior of Contracts
* How Do I Use Contract4J5?
* Distribution
* Building Contract4J5
* Show Me the Code!
* Details of Contract Specifications
* Known Limitations
* Miscellaneous Notes and Debugging Tips
* Configuration of Contract4J5
* TODO Items
* History
* Notes for Each Release
* For Further Information...
**Impatient?** If you want to get started quickly, go to
Show Me the Code!.
## Copyright
Contract4J is open source software covered by the Eclipse Public License - v1.0. A complete copy of the license is shown below and it can be found in the LICENSE-ECLIPSE.txt file in the top-level directory of the distribution.
===========================================================
Copyright 2005-2011 Contract4J Development Team
Licensed under the Eclipse Public License - v 1.0; you
may not use this software except in compliance with the
License. You may obtain a copy of the License a
http://www.eclipse.org/legal/epl-v10.html
A copy is also included with this distribution. See the
"LICENSE" file. 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.
==========================================================
In addition, several third-party components are used by Contract4J5. Their licenses are described by the LICENSE.txt file.
## Where to Get Contract4J5
Contract4J5 is hosted on [GitHub](https://github.com/deanwampler/Contract4J5) (note that the project is called *Contract4J5*). Builds can be downloaded from [SourceForge](https://sourceforge.net/project/showfiles.php?group_id=130191). The home page is [polyglotprogramming.com/contract4j](http://polyglotprogramming.com/contract4j).
**NOTE:** The previous web site for Contract4J5 was **contract4j.org**.
## Naming and Versioning
The active development project is **Contract4J5**, which uses Java 5 annotations to define the contracts. There is a separate, dormant project called **Contract4JBeans**, which was an interesting, but not very successful experiment that required no Java 5 annotations, but relied on method naming conventions to define contracts for target methods and classes. The JavaBeans-like naming conventions were the origin of the Contract4JBeans name. Contract4JBeans can be found only as a jar distribution on the [SourceForge](https://sourceforge.net/project/showfiles.php?group_id=130191) site.
The newest release of Contract4J5 described in these notes is version 0.9.0. As this is a "pre-1.0" release, it is possible that planned features and other changes may force API changes that break backwards-compatibility. However, at this point, we anticipate no such problems going forward (except where documented in the TODO Items).
## Distribution Manifest
Contract4J5 comes in *.zip and *.tar.gz formats. For convenience, each archive includes the required Jexl, Groovy, JRuby and dependent libraries. If you are using only one of the languages, a table below lists which of the jars is required for each language choice. (The Spring framework is not included. It is required to build the optional Spring-configuration example.)
After expanding the archive you will have the following directory structure:
File or Directory | Description |
contract4j5_XYZ.jar | Pre-built Contract4J5 jar. |
lib | Location of Jexl, Groovy, JRuby and dependent libraries. |
contract45 | Directory with the main distribution files |
contract4j5WithSpring | Directory with an example of configuring Contract4J5 using Spring 1.2.X or Spring 2.0. The example works with either release (it uses the 1.2.X API, which is supported by 2.0). We recommend using Spring for customizations. However, without any configuration, Contract4J5 works with suitable defaults. There is also a property configuration option, demonstrated in the JUnit tests and discussed in more detail below. |
Inside the contract4j5 directory, you will find:
ant | Directory of ant support files. |
build.bat | Window's build driver script. |
build.sh | Linux/MacOSX/Cygwin build driver script. |
build.xml | Ant build script. |
Contract4J.properties.example | Example properties file for configuring Contract4J5 using properties. Note that we recommend using Spring for this purpose. |
doc | Created during a full build; where JavaDocs go. |
env.bat | Windows environment variable setup for running the build. Invoked by build.bat. Edit this file for your environment. |
env.sh | Linux, MacOSX, Cygwin environment variable setup for running the build. Invoked by build.sh. Edit this file for your environment. |
jdepend.sh | *nix driver script for jdepend. Uses XSLT and Graphviz to generate a PNG file with the results. See the script for more information. |
jdepend.bak | Created when you run the jdepend.sh script; results from previous runs are stored here. |
jdepend.properties | Configure jdepend. |
jdepend_report.png | Example output generated by jdepend.sh. |
LICENSE.txt | Contract4J license file (Eclipse Public License 1.0). Other license files for included components may also appear in this directory. |
README.html | This README. |
src | Directory with all the non-test source files. |
src-classes | Created during a build; where the src classes go. |
test | Directory with all of Contract4J5's JUnit test sources. |
test-classes | Created during a build; where the test classes go. |
lib | 3rd-party libraries required by Contract4J5. |
## What Is "Contract4J5"?
Contract4J5 supports "Design by Contract"® programming in Java. a programming practice introduced by Bertrand Meyer and incorporated in the *Eiffel* programming language in the 1980's.
The Contract4J5 project is sponsored by [Aspect Research Associates](http://aspectresearchassociates.com), a consulting firm specializing in Scala, Data Analytics, Aspect-Oriented Programming, Enterprise Java, and Ruby on Rails.
### What is Design by Contract?
*Design by Contract*® (DbC) starts with the observation that, implicitly or explicitly, a component defines a "contract" with its clients. When a client invokes an operation on the component, it must agree to provide the component with appropriate inputs and context. Otherwise, the component can't perform its services. In return, if the input constraints are satisfied the component guarantees delivery of prescribed results.
DbC encourages the component developer to state the contract explicitly, by specifying the input constraints, known as "preconditions", and guaranteed results, known as "postconditions", in a programmatic form that can be tested at runtime. In addition, state "invariants" may be defined.
DbC is a powerful and underused tool for detecting bugs during development and testing. A key principle is that if a test fails during execution, the program terminates abruptly. While this may seem draconian, it forces the developer to solve the problem immediately, rather than allow problems to "slide", multiple, and thereby undermine the quality of the software.
Hence, during development, all tests are enabled and the code is thoroughly tested. During deployment, the tests are often disabled, both to prevent sudden shutdown and and to allow possible recovery should a contract-violating condition arise that was never detected during development. Turning off the tests also removes their overhead.
As such, DbC is a wonderful complement to Test-Driven Development, which exercises the code and hence the contract tests, thereby increasing the probability they will detect bugs. DbC tends to emphasize the fine-grained design a little more than test-driven development by itself. Designing the unit tests and specifying the contracts also force the developer to think through the details of the design before writing the code. A third technique that supports thinking through the design is to write the comment blocks for classes and methods before implementing them.
For more information on Design by Contract, see the references below.
### Design by Contract and Aspect-Oriented Programming
So what does DbC have to do with [Aspect-Oriented Software Development](http://www.aspectprogramming.com/home/aosd) (Aspect-Oriented Programming - AOP - for short)? On the one hand, the component's contract is an essential part of the complete, logical component specification that clients must support. For example, an interface for a bank account may have a contract requirement that all methods that return a balance must always return a non-negative number (ignoring overdraft features). However, in practical terms, contracts often include implementation concerns that may have little relationship to the domain logic of the application. For example, the code implementing the bank account may prohibit passing null values as method parameters.
For both types of contract details, AOP allows us to specify the details with sufficient "proximity" to the interface so that clients can see the constraints and AOP gives us an elegant way of testing the constraints at runtime without cluttering the code with logic to run the tests and handle failures.
More generally, AOP is a new approach to modularizing "concerns" that need to be handled by a component, but which tend to obscure the main logic of the component, often compromising clarity, maintainability, reusability, etc. For example, modern web and enterprise applications typically must support secure access, transactional behavior, persistence of data, and mundane support issues like logging. Without AOP, the code for these "concerns" gets mixed in with the domain logic, thereby cluttering the code and diminishing the "ilities" we all strive for. AOP keeps these concerns in separate modules and provides powerful facilities for "injecting" the concern behavior in the specific execution points where needed. Contract4J5 uses AOP techniques to find the contract specifications and test them at runtime at the appropriate execution points.
AOP is a good approach to supporting DbC because it permits DbC concerns to be managed in a modular and minimally-intrusive way, without cluttering application logic, while still allowing the contracts to be integrated into the runtime environment for development and testing. Contract4J5 uses the best-known AOP language, AspectJ, to support DbC for Java.
For more information on AOP, see the references below.
### How Does Contract4J5 Support Design by Contract?
I'm a long-time believer in DbC and wanted to use it in Java. A few years ago, I discovered the clever Barter project, which supports DbC in Java using XDoclet tags and AspectJ code generation to perform the tests as "advice".
Two problems with doclet-based approaches are that they are buried in the comments, which means they are decoupled from the runtime environment, and a preprocessor step is required. Java 5 introduced annotations, which are like "JavaDoc tags for code". In particular, annotations are used to ascribe meta-information to the code and to make that information available to the runtime environment, when desired. Annotations are a logical tool for associating contract tests with code and Contract4J5 uses them for this purpose.
The specifications are written as executable Java expressions, enclosed in the String "value()" attribute of the annotation. The details are discussed below. A contrived example suffices for now:
@Contract
public class MyClass {
@Invar ("name != null")
private String name;
@Pre ("n != null")
public void setName (String n) { name = n; }
@Post ("$return != null")
public String getName () { return name; }
@Pre ("n > 10 && s != null")
@Post ("$return != null")
public String doIt (int n, String s) {...}
...
}
The `@Contract` annotation tells Contract4J5 that `MyClass` defines tests. The tests are defined using `@Pre`, `@Post`, and `@Invar` annotations, for *precondition*, *postcondition*, and *invariant* tests respectively. In this example, the tests are:
The field `name` has an invariant test that it can never be `null` (after the object has been constructed...).
The `setName` method has a precondition that the value of the parameter cannot be `null`.
The `getName` method has a postcondition that it can never return `null`.
The `doIt` method has both a pre- and a postcondition test.
If a test fails, Contract4J5 throws an unchecked exception, `ContractError`, after printing some diagnostic information (see the unit tests for examples). A special subclass of `ContractError` is thrown if the test itself can't be evaluated for some reason (discussed in more detail below). That error is `TestSpecificationError`. So, clients who want to catch contract errors and also distinguish between these two types of failures should follow this idiom:
try {
...
} catch (TestSpecificationError tse) {
...
} catch (ContractError ce) {
...
}
The versions of Contract4J5 before V0.7 embedded the Jakarta **Jexl** interpreter, an expression evaluator, to evaluate the test expressions in the annotations. However, there are quirks and limitations of Jexl (discussed throughout this README). In V0.7, Contract4J5 added support for **Groovy** and **JRuby**, as well as Jexl, as scripting engine options. The tests exercise all three options.
In V0.8, we have **deprecated** JRuby and we may deprecate **Jexl** before the final release. As we expanded the test suite to better exercise test expressions for generic classes and test expressions that use other objects and statics in other classes, it became clear that maintaining three engines is not cost-effective, as each has its quirks.
Therefore, we have standardized on **Groovy** as the best compromise between performance and full support for Java 5 features. However, Jexl offers the best performance (about 2 times faster than Groovy, when running the test suite), so we may continue supporting it, even though Jexl doesn't appear to be as full-featured as Groovy (e.g., tests for generic classes don't seem to work).
The V0.9 release does continue support for JRuby, but removal is planned before the 1.0 release. While there is great interest in JRuby and Java interoperability, JRuby is not an ideal choice for Contract4J, because it's more useful for the syntax of the scripts to be as close to Java syntax as is "reasonable".
Here are some performance numbers, from the V0.7 release, for the different scripting engines. (The numbers are a little bigger for V0.8+, due to the increase in the number of tests in subsequent release.) The numbers compare the performance of load-time weaving (LTW) vs. binary weaving and also the performance of the JVMs in JDK 5 vs. JDK 6.
JDK 5 (sec.) | JDK 6 (sec.) |
Jexl | Goovy | JRuby | Jexl | Goovy | JRuby |
Binary | LTW | Binary | LTW | Binary | LTW | Binary | LTW | Binary | LTW | Binary | LTW |
21.7 | 98.6 | 54.9 | 324.4 | 79.6 | 189.4 | 17.7 | 62.9 | 44.8 | 133.5 | 63.2 | 108.4 |
These times are approximate "user times", averaged over a few runs, measured using the "time" shell command on a ThinkPad T42 running Ubuntu Linux. There are slightly different build activities and I/O overhead involved in the LTW numbers, adding a few percentage points to the numbers. The tests do some different manipulations depending on which interpreter is used. Hence, the results are rough, at best. Most likely, the numbers reflect the relative amounts of overhead to load the interpreters and to set up the "environments" for evaluating the scripts. Your mileage may vary...
Note that the JDK 6 performance is significantly better. The code was compiled with the JDK 5 javac and executed using the JDK 6 JVM. Ironically, we saw slightly slower performance when we compiled with JDK 6 (not shown in the table).
The Jexl test runs are roughly twice as fast as the Groovy and JRuby runs, probably because Jexl is a more "minimalist" environment, incurring less startup and execution overhead.
While not shown, we observed that the memory requirements for Groovy and JRuby are also higher. In fact, we had to increase the maximum heap size for the LTW JUnit run. We also increased the heap size for the binary JUnit runs in Eclipse, when using JRuby.
The LTW test runs take significantly longer, but the results are somewhat hard to interpret. Under JDK 5, the Jexl and Groovy runs are five to six times longer, while the JRuby runs are only about 2.5 times longer. Under JDK 6, the Jexl and Groovy runs are a little more than three times longer, while the JRuby run is under twice as long. It is not clear while JRuby and LTW performs so much better under both JDKs.
In general, the longer LTW times occur because a weaving step happens at the beginning of each TestCase, as the class is loaded. The JDK 6 speed-up is a good argument for running this VM, at least for your tests!
LTW is the most convenient way to adopt Contract4J5 (as well as other aspects, in general), but if the startup time is too long for your needs, then binary weaving of compiled code provides better performance with only a modest change in the build process.
You can select which interpreter to use at runtime by passing one of the following flags to the JVM:
-Dinterpreter=groovy (the default)
-Dinterpreter=groovybsf (Groovy using BSF - see below)
-Dinterpreter=jexl
-Dinterpreter=jexlbsf (Jexl using BSF - see below)
-Dinterpreter=jruby
If you use the Contract4J `PropertiesConfigurator`, you can use the following property:
org.contract4j5.ExpressionInterpreter=org.contract4j5.interpreter.jexl.JexlExpressionInterpreter
(Jexl example). For Spring, the tests for the include Spring example `applicationContext-contract4j5.xml` demonstrates setting Groovy as the interpreter. There are more details on language configuration below.
For more information on the differences between the different scripting languages, see the V0.7.0 release notes.
### Inheritance Behavior of Contracts
In DbC, there are rules for proper behavior of inherited contracts, based on the *Lyskov Substitution Principle* (LSP), which is a minimal definition of inheritance. Class B is considered a child class of class A, if objects of type B caN be substituted for objects of type A without breaking the program. In DbC terms, this means that class B must obey A's contract, including all the class, method, and field tests.
However, there is one nuance affecting derived preconditions and postconditions. A derived (overriding) precondition test can actually satisfy a "looser" restriction than the overridden test. Put another way, the set of valid inputs can be larger than the set for the parent (overridden) test. This is because precondition tests are tests the client must meet, so if a client already meets a strict test defined by the overridden test, then it will also satisfy a looser derived test transparently. This is "contravariant" behavior because even though subclassing is a form of increasing specialization and restriction, the restrictions imposed by the precondition test can actually grow looser.
In contrast, postcondition tests are "covariant", meaning they must be as narrow or narrower than the tests they override. This is true because postcondition tests are tests on the results the component promises to deliver, as opposed to tests on clients. So, if the client is expecting a result in a set of possible results and a derived test narrows the set further, then the result will still satisfy the client's expectations.
Contract4J5 provides only minimal support for contravariant precondition tests and covariant postcondition tests. First, because Java 5 annotations on methods are NOT inherited, it is a requirement for writers of subclass method overrides to also include the annotations on the parent method. However, the annotations do not have to reproduce the test expressions. C4J5 will locate the corresponding parent class test expressions automatically. In contrast, class-level invariants are inherited, since class annotations can be inherited. (However, it is harmless to repeat those in subclasses, too.)
C4J5 attempts to enforce the rule that invariant tests can't change. However, it uses a simple string comparison, ignoring whitespace, so some logically equivalent expressions may get flagged incorrectly as different. For example:
a == b vs. b == a
will appear to be different, when they are logically equivalent.
To properly write contravariant precondition tests and covariant postcondition tests, you will have to repeat the "inherited" test expression and "append" the appropriate refinements, e.g.,
@Pre("new_test || parent_test")
@Post("new_test && parent_test")
However, since the parent test is always valid for the derived method override, if you don't need to modify the test, then you can simply use the `@Pre` or `@Post` annotation without a test expression and C4J5 will find the parent expression.
We are considering ways to support the correct inheritance behavior before the 1.0 release of Contract4J5.
### How Do I Use Contract4J5?
The distribution contains ant files and examples of how to use Contract4J5. The examples are actually part of the unit/acceptance test suite. If you are using Eclipse, the project configuration files are also in the source distribution. You may or may not want to remove them.
**NOTE:** The tests/examples are the best way to see how to write test expressions correctly!
Installation and Configuration:
For Linux/Unix systems, use these commands:
1) cd ~/work # or wherever...
3) cp .../contract4j5_XYZ.tar.gz .
4) tar xvzf contract4j5_XYZ.tar.gz
On Windows systems, Unzip the zip file to an appropriate location.
You will need to have Java 5 or Java 6 and AspectJ 5 installed to use Contract4J5. You will need to select which scripting language you prefer for the annotation scripts used to define contracts. (See the notes above about plans to deprecate JRuby and possibly Jexl.) The distribution includes all the currently-supported options; Jakarta Commons Jexl 1.1, Groovy 1.0, and JRuby 1.0. (Several other jars, such as Commons Logging, are also included, which are required by the scripting engines).
If you build Contract4J5 yourself, you will also need JUnit 3.8.X.
**Note:** You must use AspectJ v1.5.3 or later if you want to use load-time weaving (LTW) with Contract4J5.
Once you have started adding contract tests to your code, there are three ways to enable Contract4J5's contract enforcement:
1. Compile our code with AspectJ's `ajc` compiler instead of `javac`. You will need to add the `contract4j5.jar` to your `CLASSPATH`.
2. Use a *binary weaving* step. After you compile your code with `javac`, use `ajc` to weave in the Contract4J5 aspects.
3. Use *load-time weaving* (LTW) to apply the Contract4J5 aspects at runtime.
All three approaches are demonstrated by Contract4J5's ant build process, as described below. We describe load-time weaving (LTW) here. It is the easiest and the least intrusive approach to adopt and we recommend you start with it. Recall, however, from the discussion above, that the performance of LTW is slower than "binary" weaving (discussed below), so you may wish to convert to that approach at a later time.
To use LTW, follow these steps.
1. Install AspectJ v1.5.3 or later.
2. Make sure the `ASPECTJ_HOME` environment variable is defined.
3. Add the `contract4j5.jar` to your `CLASSPATH` and your `ASPECTPATH`, if using Eclipse or `ajc` (i.e., `-aspectpath` ...).
4. Copy and adapt the `test/META-INF/aop.xml` file to your project.
5. Invoke your application (e.g., JUnit tests) using the `ASPECTJ_HOME/bin/aj5` (*nix) or `%ASPECTJ_HOME%\bin\aj5.bat` (Windows) script, instead of java.
Now the Contract4J5 aspects will be applied as your application's classes as they are loaded. For production deployments, simply use java to invoke your application, as before.
**Note:** To see how to invoke JUnit tests from ant with load-time weaving (LTW), see the `_junitTemplate.ltw` build target in `ant/targets.xml`. This example also provides more details on how to use LTW with Contract4J5.
If you are already using the AspectJ `ajc` compiler to compile your Java and AspectJ sources, then simply include `contract4j5.jar` in the `-aspectpath` argument to `ajc`.
Another alternative, whether you use `ajc` or `javac` to build your code, is to add a final *binary weaving* step to your build.
To see how to do a binary weaving step, consult the `ant` build files in the distribution, in particular, the `test` target and dependencies. The `compile.test` target uses `javac` to compile the JUnit test code. The `project-test.jar` target uses `ajc` to do binary weaving, where the compiled class files are read by `ajc`, aspects are woven into them (note the `-aspectpath` option to `ajc`) and the `contract4j5-test.jar` file is output. The `ant/targets.xml` defines the `binaryWeaveTemplate` target used for this process.
**Note:** To see what the `ajc` command does, invoke ant with the option `-Dbuild.compiler.verbose=true` which will cause `ajc` to print the command-line options used. Look for the output that is part of the `compile.test` target. This output may be easier to understand than trying to understand the ant files!
You can use the installed `contract4j5.jar` file as is. If you want to rebuild Contract4J5, use the ant driver script `build.sh` or `build.bat`. First, edit the corresponding `env.sh` or `env.bat` file and change the environment variable definitions as appropriate for your environment. Or, you can define the appropriate environment variables in your environment and use the `build.xml` ant script directly.
## Distribution
The distribution has the following structure:
File/Directory | Description |
README.html | This file. |
LICENSE.txt | The Apache 2 license file for Contract4J5. |
build.sh | Unix/Linux build driver script. Sets "home" variables where the tools are found. Edit to taste. |
build.bat | Windows build driver script. Also sets "home" variables... |
build.xml | Ant build script. |
src | The source code tree. |
test | The JUnit unit/acceptance tests, which also function as usage examples. The files ending with "*Test.java" are JUnit tests. The other classes under "test" are example classes used by the tests, which also provide C4J5 usage examples. The JUnit test files often contain additional example classes that demonstrate usage and they contain comments about tests the demonstrate known idiosyncrasies or limitations of C4J5 and script evaluation using Jexl, Groovy, and JRuby. |
classes | Where build artifacts (except the jars) are stored.
|
doc | Where Javadocs are written. |
contract4j5.jar | The runtime deployment jar. It contains the build products from "src". |
contract4j5-test.jar | The jar containing the build products from "test". Not part of the normal runtime deployment. |
## Building Contract4J5
If you want to build Contract4J5:
./build.sh all # *nix
build.bat all # windows
or
ant all
The jar files `contract4j5.jar` and `contract4j5-test.jar` in the current directory will be built and the unit/acceptance tests will be executed for all three supported languages. The tests generate a LOT of output, but they should all pass. Also, there will be some warnings that fall into two categories:
Warnings in some unit tests when test annotations are used without the required `@Contract` annotation. This is deliberate for those tests.
The `javadocs` target also results in many warnings for references to aspects from Java files, which `javadoc` doesn't know how to resolve. To be clear, the following missing "classes" are actually aspects:
* `AbstractConditions`
* `ConstructorBoundaryConditions`
* `Invariant*Conditions` (several)
* `MethodBoundaryConditions`
For example, you'll see lots of warnings about not being able to find members of these aspects.
If the unit tests fail, look for output in `contract4j5/TEST-*.txt` files. Usually, the problem will be a `CLASSPATH` issue.
To build the example of wiring the components and properties of Contract4J5 using **Spring's Dependency Injection** run any of the following commands after building `all` as before:
./build.sh all.spring # *nix
build.bat all.spring # windows
or
ant all.spring
This will run a test target that confirms that Spring can "wire" Contract4J correctly.
This build target builds the example in the "sister" directory `../Contract4J5WithSpring`. It contains a separate example demonstrating how to use the Spring Framework's Dependency Injection (DI) to configure the properties of Contract4J. This is done separately from the main build so that Spring is not required for those people not using it.
The key files in this directory tree are the following:
test/org/contract4j5/configurator/spring/test/ConstructWithSpringTest.java | Uses Spring's "ApplicationContext" to construct C4J, then tests that the components and properties are wired as expected. |
test/conf/applicationContext-contract4j5.xml | The application context configuration file that defines the "wiring". |
test/conf/contract4j.properties | A properties files used by the config. file. |
An example of running the test suite using Contract4J5 with Load-Time Weaving (LTW) is also included. (The details of using LTW were discussed above.)
**Note:** LTW requires AspectJ 1.5.3 or newer, due to a bug in early versions.
To run the tests using load-time weaving, first build all as above, then build the following target:
./build.sh test.ltw # *nix
build.bat test.ltw # windows
or
ant test.ltw
This target will run the tests, using LTW of the aspects on the fly.
#### More Details on Building Contract4J5
The following third-party tools are required, along with corresponding `HOME` environment variable definitions needed by the ant build scripts:
* JUnit 3.8.1+ (`JUNIT_HOME`)
* Java 5 (`JAVA_HOME`)
* AspectJ 1.5.3+ (`ASPECTJ_HOME`)
* Ant 1.6.5+ (`ANT_HOME`)
* Jexl 1.1+ (included in the lib directory)
* JRuby 1.0+ (included in the lib directory)
* Groovy 1.0+ (included in the lib directory)
* Other support libraries in the lib directory.
* Also define `Contract4J5_HOME` to be the `.../contract4j5_XYZ/contract4j5` directory where you installed it.
For your convenience, you can use the build driver script `build.sh` or `build.bat`. Edit the values of the environment variables in the corresponding scripts `env.sh` or `env.bat` for your environment.
Only Java, AspectJ, the libraries for one of the scripting engines, and the support jars it requires are needed if you simply use the binary contract4j5.jar in the distribution. (All the scripting engines jars are in the lib directory.) Make sure your build process compiles with AspectJ, weaves your precompiled jars or class files with contract4j5.jar, or uses load-time weaving, as demonstrated in the "LTW" tests.
Next, we'll look at code examples, then return to a discussion of invoking and configuring Contract4J5.
## Show Me the Code!
Here is a large example showing how to define Contract4J5 tests in your code so that Contract4J5 can discover and execute them at runtime. It is the file `test/org/Contract4J5/test/BaseTestClass.java` (with some superfluous details omitted). See additional examples in the unit/acceptance suite under the "test" directory.
The comments in this class should be self explanatory. More specific details on writing contracts are provided below.
`BaseTestClass.java`:
package org.contract4j5.test;
import org.contract4j5.contract.*;
/**
* A (contrived) example Java class that demonstrates how
* to define DbC tests. The "@Contract" annotation is
* required. Then, we define a class-level invariant,
* which happens to be for one of the fields. Note that
* we have to prefix the field name with "$this", one of
* several special keywords that begin with "$" and are
* replaced with special values before passing the
* expression to the script interpreter. In this case,
* "$this" means "this object" (You can't just use "this"
* without the "$" for backwards compatibility reasons;
* this may be relaxed in a future release).
* Prefixing field names with $this is necessary for the
* scripting engine to be able to resolve the variable
* name. While not required in all cases, as a rule it is
* best to always refer to fields this way for consistent.
* The one case where you don't need the "$this." is when
* you define an invariant for a field itself (See the
* test for "name" below). Note also that in order for
* Jexl to resolve the field reference, a JavaBeans
* "getter" method must exist for the field, even if the
* field is public!
*/
@Contract
@Invar("$this.lazyPi==3.14159") // see comments for "lazyPi"
public class BaseTestClass {
/**
* A field that is initialized "lazily", but cannot
* change after that. This invariance is enforced by the
* @Invar annotation on the class. The constructor must
* call {@link #getLazyPi()} BEFORE ANY OTHER PUBLIC
* FUNCTION, or the invariant test will fail!
* NOTE: the Jexl parser chokes if the invariant test
* appends "f" to the constant!
* NOTE: Jexl can't resolve "lazyPi" unless "getLazyPi()"
* exists!
*/
private float lazyPi = -1f;
/**
* "getLazyPi()" always simply sets the value to 3.14159,
* so the class invariant "$this.lazyPi==3.14159" will
* always pass. However, see {@link #setLazyPi(float)}.
* @return pi
*/
public float getLazyPi() {
if (lazyPi == -1f) {
lazyPi = 3.14159f;
}
return lazyPi;
}
/**
* This function allows unit tests to force a failure!
*/
public void setLazyPi (float f) {
lazyPi = f;
}
/**
* A field that should never be null or "". See also
* comments in {@link #setName(String)}. Note that you
* can safely use the "bare" field name "name" here.
* You can also use "$this.name", which you have to use
* in all other types of tests (i.e., tests other than
* the invariant test on the field itself). You can also
* use the keyword "$target", which currently is only
* used to refer to a corresponding field when used in a
* test expression. (In the future, "$target" may have
* other uses in the more general AspectJ-sense of the
* poincut "target()" expression.)
* NOTE: You can specify an optional error message that
* will be reported with any failure message. Also, as
* stated before, "name" must have a "getName()"
* accessor or Jexl can't resolve it!
*/
@Invar(value="name != null && name.length() > 0",
message="this.name must never be null!")
private String name;
/**
* @return String name of the object
*/
public String getName() { return this.name; }
/**
* Use a precondition to prevent setting name to null.
* Note this test is less restrictive than the invariant
* test on the field itself, a poor design. (Hopefully,
* the developer will realize the mistake when one test
* fails while the other passes.) In this case, this
* "mistake" is useful for the dbc4j unit tests.
* @param name String naming the object
*/
@Pre("name != null")
public void setName (String name) { this.name = name; }
// A flag; used for other contract tests.
private boolean flag;
/**
* Set the flag.
*/
public void setFlag () { flag = true; }
/**
* Set the flag. This method is used in unit tests to
* force a contract assertion failures.
*/
public void setFlag (boolean f) { flag = f; }
/**
* Constructor. Note that the precondition on the "name"
* parameter is redundant, since {@link #setName(String)}
* is called, but it is still useful for documenting the
* interface. Note that the @Pre test does not
* define a test expression. In this case, C4J5 uses a
* {@link org.contract4j5.testexpression.DefaultTestExpressionMaker}
* to generate a default test expression. There are
* separate "makers" for different types of tests and
* contexts and they are user configurable. For
* preconditions, the default is to require that all
* arguments are non-null.
* Note that tests can call methods, too, but watch for
* side effects, especially since tests will normally be
* disabled in production builds. Therefore, never call
* a method with side effects!
* @param name a non-null String
*/
@Pre
@Post ("$this.isValid() == true")
public BaseTestClass (String name) {
/* float ignore = */ getLazyPi();
setName (name);
setFlag ();
}
/**
* Constructor. As discussed in {@link
* #BaseTestClass(String)}, the default test expression
* for the precondition test will be that all parameters,
* in this case "name" and "flag", must be non-null. What
* does that mean for "flag", which is boolean. Not much;
* this argument will be converted to
* {@link java.lang.Boolean} internally and it will never
* be null! Also, in this example, the precondition test
* is actually redundant, since {@link #setName(String)}
* is called. However, the test is still useful for
* documenting the interface.
* @param name a non-null String
* @param flag a boolean flag; if false, causes the
* postcondition to fail.
*/
@Pre
@Post ("$this.isValid() == true") // watch for side effects!
public BaseTestClass (String name, boolean flag) {
/* float ignore = */ getLazyPi();
setName (name);
setFlag (flag);
}
/**
* Is the object valid?
*/
public boolean isValid () {
System.out.println ("ExampleClass.isValid(): flag: "+flag);
return flag; // reusing our flag...
}
/**
* Method that requires flag to have been previously set.
* E.g., {@link #setFlag(boolean)}, {@link #doIt()}, etc.
* Note the postcondition to confirm that the method
* succeeded, where "$return" is the keyword that matches
* the value returned by the method (an int in this case).
*/
@Pre(value="$this.flag == true",
message="this.flag true before calling 'doIt()'?")
@Post("$return == 0")
public int doIt () {
if (name != null && name.equals("bad name")) {
return 1;
}
return 0;
}
/**
* Overloaded method. Useful to confirm that the
* generated tests correctly discriminate between the
* methods (note the conflicting @Post annotations on the
* two versions.)
*/
@Post("$return != 0")
public int doIt (int toss) {
if (name.equals("good name")) {
return 1;
}
return 0;
}
/**
* Method with tests on more than one parameter. The
* keywords "$args[n]" refer to the parameter arguments,
* counting from 0.
*/
@Pre ("$args[0]> 0 && $args[1].equals(\"foo\")")
public int doThat (int toss, String fooStr) {
return toss;
}
/**
* Method with tests on more than one parameter. Tests
* whether we correctly generate matching aspects on the
* second and last parameter. Note that a nested string
* in a test must be escaped.
*/
@Pre ("toss2 > 0 && toss4.equals(\"foo\")")
public int doTheOther (int toss1, int toss2,
String toss3, String toss4) {
return toss1;
}
/**
* Test Contract4J5 with a nested class
*/
@Contract()
public static class NestedBaseTestClass {
private String name;
@Post
public String getName() {
return name;
}
@Pre
public void setName(String name) {
this.name = name;
}
@Invar ("$target > 0")
private int positive;
/**
* Method to force the invariant test to fail,
* if a negative argument is used.
*/
public void setPositive (int p) { this.positive = p; }
public int getPositive () { return this.positive; }
// The @Post on "name" should really be a @Pre on "nm",
// as it is more restrictive, but it is useful for
// example purposes.
@Post ("$this.name != null && $this.name.length() > 0 && nm != null")
NestedBaseTestClass (String nm) {
this.name = nm;
this.positive = nm != null ? nm.length() : -1;
}
}
}
## Details of Contract Specifications
Here are the rules for using Contract4J5, which clarify the examples just discussed.
### Annotate Interfaces and Classes with `@Contract`
Examples:
@Contract
public interface Foo { ... }
@Contract
public class Bar { ... }
Any interface or class that declares a contract must be annotated with `@Contract`. Otherwise, the tests will be ignored. C4J5 will issue a warning during compilation, but if you use `javac` to compile and then weave later with `ajc`, you may not get any warnings and the tests will be silently ignored.
The annotation must also appear on any derived interfaces or classes if they define new tests.
### Define Class Invariants
Examples:
@Contract
@Invar("boolean_test_expression")
public interface Foo { ... }
@Contract
@Invar(value="boolean_test_expression", message="The test failed")
public class Bar { ... }
The second example shows the optional "message" that will be reported on error, in addition to standard messages. The test expression must return boolean, or it will be treated as a contract failure!
For subclasses, it isn't necessary to annotate them, too, because class-level annotations are inherited, but you can do so for consistency with method annotations, which aren't inherited and must be added to subclass overrides.
Define Field Invariants
Examples:
@Contract
public class Bar {
@Invar("name != null && name.length() > 0")
private String name;
public String getName() { return name; }
...
}
Note that field invariants can't be defined on interfaces, since they don't have mutable fields, but you can simulate the same thing by annotating corresponding accessor methods in the interfaces (see below).
Note that for the field `name`, we are able to use the "bare" field name when defining an invariant test for it. You can also use `$this.name` or the `$target` keyword.
**WARNING!** As discussed previously, the JEXL interpreter can only resolve the field if a JavaBeans "getter" method is defined for it, as shown in the example.
In the future, `$target` may be used more generally for objects that correspond to AspectJ's `target()` *pointcut* expression, but currently `$target` is only used in field invariant tests to refer to the field.
### Define Method and Constructor Preconditions, Postconditions, and Invariants
**NOTE:** we have to use `$this.name` in the following interface example, not just `name` by itself, because we are no longer defining a field invariant test! This is because **Jexl requires a field getter method is required for Jexl to resolve the field reference.** (We recommend the same convention for JRuby and Groovy, too.)
Examples:
@Contract
public interface Foo {
@Invar("$this.name != null && $this.name.length() > 0")
String getName();
@Invar("$this.name != null && $this.name.length() > 0")
void setName (String s);
@Pre("$args[0] > 0")
@Post("$this.i = $old($this.i) + $args[0]")
void incrementI (int amount);
...
}
@Contract
public class FooImpl implements Foo {
@Invar
public String getName() {...}
@Invar
public void setName (String s) {...}
@Pre @Post
void incrementI (int amount) {...}
int getI() { ... } // getter method required by Jexl!
...
}
@Contract
public class Bar {
private float factor;
@Pre("fudge > 1.0")
@Post("$this.factor > 1.0")
void addFudgeFactor (float fudge) {...}
@Pre("factor > 0.0")
@Post("$this.factor > 0.0")
public Bar (float factor) {
this.factor = factor;
}
float getFactor() { ... } // getter required by Jexl!
}
The `Foo` interface simulates a field invariant test on an implied name field by defining invariant tests on name's accessor methods. The precondition test for `incrementI` requires the input amount to be positive. (You could also write this test `amount > 0`, but this is actually less robust; it is more likely to trip over parsing idiosyncrasies in Contract4J5 and Jexl.) This test implies an `int i` field, as does the post condition test which requires that the new value of `i` be equal to the old value (grabbed by the `$old($this.i)` expression) plus the amount.
**NOTE:** The `$this` cannot appear in `@Pre` tests on constructors, as the object doesn't exist yet! However, `$this` is allowed in `@Invar` tests on classes and constructors, as they won't be evaluated until after the constructor finishes executing.
The `FooImpl` implementing class *must* repeat the annotations, but it can omit the test expressions. Contract4J5 will determine the expressions from the parent class or interface.
What if you omit the expressions? Currently, there is no compile-time checking possible the tests will not be evaluated on these method implementations. For subclass overrides of parent class methods, the tests will not be evaluated on the overrides, but they will be evaluated on the parent methods if `super` is called.
**NOTE:** A desired future enhancement is to either enforce proper usage of the annotations on implementing classes or subclasses or else evaluate the tests even when they are absent!
The `Bar` class shows that tests can also be defined for constructors. (Invariant tests are also allowed, but seldom useful, since contract tests mostly focus on object state, which won't exist before construction!)
Note the idiom for the `@Pre` and `@Post` test expressions for the `Bar` constructor. The `@Pre` expression references the `factor` parameter, while the `@Post` expression references the factor field, using the `$this.` prefix. A common mistake is to omit the `$this.` causing the test to be executed with the parameter value instead.
**NOTE:** Jexl appears to choke if floats have a trailing `f`. As shown in the example, they should be omitted.
### Special Keywords
The previous examples use the special keywords. Contract4J5 substitutes the correct values before invoking the scripting engine to evaluate the expressions. Here is a description of the keywords and their proper use.
Keyword | Usage |
$this | The "this" object under test. |
$target | A field in an invariant test. There must be a corresponding JavaBeans "getter" method or Jexl won't be able to resolve the field. |
$return | The return result of a method; only valid in postconditions.
|
$args[n] | The "nth" argument in a parameter list. |
$old(..) | The "old" value (before a method is actually executed) of the contents of the expression, which can be one of the following:
$old($this) | Not recommended, because only the reference is saved and the object pointed to by "this" may change! Use fields or method calls instead. |
$old($target) | Equal to $old($this.field). Be careful if "field" is mutable; the value is not saved, just the reference to the object! |
$old($this.field) | Recommended usage, if "field" is primitive, in which case the value is captured, or it refers to an immutable object. Same for $old($target.otherField). |
$old($this.method(x,y)) | The returned value is saved. Due to parser limitations, method calls may not contain nested method calls. |
|
The most important thing to remember about `$old(..)` is that Contract4J5 only remembers the value, which may be a reference to a mutable object. (We can't rely on `clone()` working.) Try to use it only with primitives or immutable objects like strings.
### Test Expression Best Practices
This section outlines the right way to write test expressions, reflecting the limitations of Contract4J5 and the different supported scripting engines. For detailed examples, see the code in the test suite (e.g., `BSFExpressionInterpreterAdapterExpressionEvalTest.java`).
#### Default Test Expressions
If a contract annotation is used with no test expression, a default expression will be inferred with possible. For cases where no test can be inferred, it is considered an error, but this can be overridden with an API call:
ExpressionInterpreter.setTreatEmptyTestExpressionAsValidTest(boolean);
If the test is on an element with no superclass equivalent, the following default rules apply:
@Pre | All arguments are expected to be non-null, which means there is no meaningful default test for primitive arguments. |
@Post | The return value is expected to be non-null, unless the method returns void. |
@Invar | There is no default expression except for field invariants, where the field is expected to be non-null. |
For elements with superclass equivalents, the following rules apply. First, recall that preconditions and postconditions can only be used on methods and constructors. Also, because Java5 method annotations are never inherited, you *must* annotate any method with the same annotations found on its parent. However, the test expressions can be empty and if so, the corresponding test defined in the parent element will be used. Note that if you don't put the annotations on derived class overrides, if they call the parent methods, the parent methods will be tested, but not anything the override does (including constructors).
API calls exist in the `org.contract4j5.aspects..*.aj` aspects to specify customized objects for calculating a default expression at runtime. These objects must implement
org.contract4j5.testexpression.DefaultTestExpressionMaker.
#### Inheritance Rules for Annotation Test Expressions
This was discussed in depth above, in the section titled How Does Contract4J5 Support Design by Contract?.
* `$this` refers to the object being tested.
You can call any public method on the object in the test expression; the scripting engine will resolve the type. Additionally, if you refer to a bare field that is not public, the scripting engine will convert the expression to the corresponding "getter" call. Unfortunately, it appears that the getter method is actually required in order for Jexl to resolve the field; it won't just use the bare field directly.
* `$target` currently is used only to refer to the field in a field invariant test.
Future use may include any context associated with the `target()` *pointcut* expression. Just as for `$this`, you can reference any method or field defined for the object.
* `$return` is the value returned by a method.
It is only valid in postcondition tests. As for `$this`, you can reference any method or field defined for the object.
* `$args[]` are the arguments passed to a method, indexed starting at zero.
You can also use the declared argument name. However, if the name shadows an instance field, the parser may confuse the two; use the appropriate `$arg[n]` in this case. As for `$this`, you can reference any method or field defined for the objects in the array.
* Use of the `$old(..)` Keyword
The "old" `$old(..)` keyword tells Contract4J5 to remember the value of the contained expression before evaluating evaluating the join point, so that value can be compared to the "new" value after evaluating the join point. It can only be used in `@Invar` and `@Post` condition tests and the saved value is forgotten once the test completes.
The most important thing to remember about `$old(..)` is that Contract4J5 only remembers the value, which may be a reference to a mutable object. Since `clone()` is not guaranteed by Java to be publicly available on an object, we can't clone it and it was deemed too "obscure" to only permit, for example `$old($this)` on objects where clone is publicly available. Hence, you should try to use the `$old` keyword only with primitives or references to immutable like strings.
Here are the allowed expressions.
$old($this) | Not recommended, since only the reference is saved. |
$old($target) | Equal to $old($this.field). Be careful if "field" is a reference to a mutable object! |
$old($this.field) | Recommended usage, if "field" is primitive. A synonym for $old($target) when used in a field invariant test. |
$old($this.method(x,y)) | Method call where the returned value is saved. Due to current parser limitations, nested method calls are not supported. So, the following is okay:
$old($this.getFoo().doIt(1))
but this is not
$old($this.getFoo().doIt(getIntI())) |
* References to Instance Fields.
For fields, Jexl, Groovy, and JRuby will automatically convert a "bare" field reference to its accessor, even if the field is private. Hence, an expression like
$this.foo.bar.baz.doIt(1)
is allowed and will be translated to
$this.getFoo().getBar().getBaz().doIt(1)
**Note:** In fact, for Jexl the corresponding getter methods are *required* or Jexl will not be able to resolve the field references.
Normally, you should prepend `$this.` before a "bare" field reference as the parser does not always correctly resolve the reference to an instance field. The one case where $this is unnecessary is inside a field invariant test. Using the field's "bare" name will work. As an alternative in field invariant tests, $target can be used to refer to the field.
Note the example previously where a field invariant test was written with the bare field called `name`, but when a set of "conceptually similar" `@Pre` and `@Post` tests were written in an interface on `setName()` and `getName()` methods, it was necessary to use `$this.name`. Contract4J5 may not resolve the field correctly in those cases.
* Tests Defined on Interfaces
You can define tests on interfaces and their methods. In fact, you are urged to do so. Unfortunately, for reasons discussed previously, you must include the same annotations, although not their test expressions, on the declarations of the method implementations in implementing classes. Contract4J5 will find the test expressions in the interfaces.
Unfortunately, you can't define contract tests for constructors, since they don't exist in an interface. You may be able to work around this using class invariants and tests on instance methods. Note that you can also implicitly define field invariant tests, either on declared accessor methods or as invariants on the class itself. You can refer to the (implied) bare field in the test, as long as you declare an appropriate accessor method for it.
### Known Limitations
(New for V0.8.0) See also Miscellaneous Notes and Debugging Tips and Inheritance Behavior of Contracts.
Referencing other objects, classes, and static methods/fields in test expressions. The scripting engines all run in a different "context" from your running classes. This means that simply referring to an object in an expression doesn't guarantee that the script will see it, even if the same reference would compile if it were in regular Java code appearing in the class under test. Hence, there are limitations and they vary from one scripting engine to the other. Contract4J offers some help and where it won't be able to resolve a reference, there are a few workarounds. Here is a summary of the behavior for Groovy:
* You can reference another instance field or method without the prefix $this..
* You can reference a static field or method in the class under test, but you must prefix it with $this.. This appears to be a limitation of Groovy.
* You can reference another class in the same package as the class under test, without qualifying it with its package.
* Except for Groovy, you can reference another class in a different package with its fully qualified name. However, nested classes don't work, because you have to use '$' instead of '.', as Contract4J will attempt to load the class in the scripting context using `Class.forName(...)`, which requires the '$' delimiter (e.g., `com.foo.bar.MyClass$MyNestedClass`). However, when the expression is evaluated later, some scripting engines will choke on the '$' (catch-22...). **Workaround:** use a validation method in your class under test to invoke the static method or use one of the other workarounds described here. Groovy interprets the fully-qualified names as strings of property queries.
For Groovy and JRuby (not Jexl), you can "preregister" classes or objects in your static initializer block or constructor. Using the static initializer block will usually be the easiest approach, especially if you need the classes or objects in constructor precondition tests. You can register the classes or objects in the constructor if they are only needed by subsequent calls to instance methods. JRuby can only reference "global" variables. Here is an example for Groovy:
// In Validator.java
package com.foo.bar1;
public class Validator {
public static boolean called = false;
public static boolean valid(String s) { return s != null && s.length() > 0; }
public boolean valid2(String s) { return valid(s); }
}
// In MyClass1.java
package com.foo.bar2; // different package
class MyClass1 {
static {
Contract4J.getInstance().registerGlobalContextObject("Validator", com.foo.bar1.Validator.class);
}
@Pre("Validator.valid(name)")
public MyClass1(String name) {...}
}
// In MyClass2.java
package com.foo.bar2; // different package
import com.foo.bar1.Validator;
class MyClass2 {
static {
Contract4J.getInstance().registerGlobalContextObject("validator", new Validator());
}
@Pre("validator.valid2(name)")
public MyClass2(String name) {...}
}
* For JRuby, the previous test expressions must put a '$' before the Validator and the validator references. See `test.org.contract4j5.aspects.constructor.test.ConstructorBoundary*ExpressionsWithObjectReferencesTest` classes for more examples for the different languages.
* JRuby support is deprecated and will be removed in a future release.
* Jexl support may be deprecated, but keeping it provides a faster, if limited, scripting alternative.
* It appears that Jexl and JRuby **can't handle Java 5 generic objects**. The Contract4J unit tests with generics are effectively "no-ops" except when Groovy is the interpreter.
### Miscellaneous Notes and Debugging Tips
* All test expressions *must* evaluate to a **boolean** value.
* Test expressions that fail to be evaluated by the scripting engine are treated as test failures, on the grounds that the expression is buggy in this case! Note that if an annotation is empty (i.e., it doesn't define a test expression), then it is considered an error if a default expression can't be inferred and no corresponding test exists on a parent class. However, there is an API call to allow empty tests, `ExpressionInterpreter.setTreatEmptyTestExpressionAsValidTest(boolean)` in the `org.contract4j5.interpreter` package).
* When a test fails due to a buggy or empty test expression, as just described, a subclass of `ContractError` is thrown, `TestSpecificationError` (new as of v0.6.0).
* Remember that when using Jexl, if any test accesses an instance field, the field must have a corresponding JavaBeans getter method. Otherwise, Jexl will fail to resolve the field and the test will fail.
* Fields in expressions must be prefixed with ., except in field `@Invar` test expressions. Jexl is more forgiving if you omit the prefix.
* Avoid expressions with side effects. Since tests will usually be turned off in production, test expressions with side effects, e.g., assignments, will not be evaluated, thereby changing the logical behavior of the application.
When using load-time weaving (LTW), if it appears that the tests aren't being evaluated, make sure you have an `aop.xml` file in your class path. Adapt the example file used for the LTW tests.
* Because runtime expression evaluation is very slow compared to compiled code, consider embedding non-trivial tests in "validation" methods and calling them from the test expression. (Prepend instance tests with `$this.`)
* Keywords follow the same white space rules as for Java. Don't allow whitespace between `$` and the keyword names.
* Jexl can't parse literal floats and doubles with the 'f' and 'd' appended, respectively. Leave them off in both cases.
* Most other Java expressions, like comparisons and arithmetic expressions can be used. See the Jexl website for more information on allowed expressions.
* Before passing the expressions to Jexl, substitutions are made. Normally, you shouldn't case, but when debugging, you may see strings with these substitutions. All the '' keywords are changed. For example,
$this | becomes | c4jThis |
$target | becomes | c4jTarget |
$old($this) | becomes | c4jOldThis |
$old($target) | becomes | c4jOldTarget |
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。