[med-svn] [libcofoja-java] 01/02: Imported Upstream version 1.1-r150
Andreas Tille
tille at debian.org
Sat Mar 8 22:27:40 UTC 2014
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository libcofoja-java.
commit e43f28e2a16b800a606cef8a5f1ec354acd713c4
Author: Andreas Tille <tille at debian.org>
Date: Sat Mar 8 23:25:56 2014 +0100
Imported Upstream version 1.1-r150
---
COPYING | 165 ++++++
README | 136 +++++
build.xml | 257 +++++++++
build/PreAgentAntTask.java | 81 +++
default.properties | 23 +
script/cofojab.sh | 94 +++
script/cofojac.sh | 133 +++++
src/META-INF/MANIFEST.MF | 2 +
.../services/javax.annotation.processing.Processor | 1 +
.../google/java/contract/AllowUnusedImport.java | 39 ++
.../java/contract/ContractAssertionError.java | 111 ++++
.../google/java/contract/ContractEnvironment.java | 191 +++++++
.../contract/ContractEnvironmentConfigurator.java | 38 ++
src/com/google/java/contract/Ensures.java | 75 +++
src/com/google/java/contract/Invariant.java | 49 ++
src/com/google/java/contract/InvariantError.java | 53 ++
.../google/java/contract/PostconditionError.java | 53 ++
.../google/java/contract/PreconditionError.java | 55 ++
src/com/google/java/contract/Requires.java | 51 ++
.../google/java/contract/SpecificationError.java | 36 ++
src/com/google/java/contract/ThrowEnsures.java | 65 +++
.../contract/core/agent/ActivationRuleManager.java | 124 ++++
.../core/agent/AgentContractEnvironment.java | 95 ++++
.../contract/core/agent/ClassContractHandle.java | 59 ++
.../java/contract/core/agent/ContractAnalyzer.java | 288 ++++++++++
.../core/agent/ContractClassFileTransformer.java | 408 +++++++++++++
.../core/agent/ContractFixingClassAdapter.java | 94 +++
.../java/contract/core/agent/ContractHandle.java | 127 +++++
.../core/agent/ContractMethodSignature.java | 61 ++
.../core/agent/ContractMethodSignatures.java | 166 ++++++
.../contract/core/agent/HelperClassAdapter.java | 104 ++++
.../core/agent/LineNumberingClassAdapter.java | 69 +++
.../core/agent/LineNumberingMethodAdapter.java | 110 ++++
.../contract/core/agent/MethodContractHandle.java | 68 +++
.../google/java/contract/core/agent/PreMain.java | 232 ++++++++
.../core/agent/SpecificationClassAdapter.java | 123 ++++
.../core/agent/SpecificationMethodAdapter.java | 632 +++++++++++++++++++++
.../contract/core/apt/AbstractTypeBuilder.java | 292 ++++++++++
.../contract/core/apt/AnnotationProcessor.java | 408 +++++++++++++
.../contract/core/apt/AnnotationSourceInfo.java | 94 +++
.../contract/core/apt/ClassContractCreator.java | 121 ++++
.../java/contract/core/apt/ContractCreation.java | 528 +++++++++++++++++
.../contract/core/apt/ContractCreationTrait.java | 111 ++++
.../core/apt/ContractExpressionCreationTrait.java | 73 +++
.../core/apt/ContractExpressionTransformer.java | 369 ++++++++++++
.../java/contract/core/apt/ContractFinder.java | 175 ++++++
.../contract/core/apt/ContractJavaCompiler.java | 124 ++++
.../contract/core/apt/ContractJavaFileManager.java | 127 +++++
.../java/contract/core/apt/ContractWriter.java | 629 ++++++++++++++++++++
.../java/contract/core/apt/DiagnosticManager.java | 525 +++++++++++++++++
.../java/contract/core/apt/FactoryUtils.java | 167 ++++++
.../google/java/contract/core/apt/JavacUtils.java | 131 +++++
.../contract/core/apt/MethodContractCreator.java | 359 ++++++++++++
.../core/apt/SimpleContractCreationTrait.java | 80 +++
.../contract/core/apt/SourceDependencyParser.java | 212 +++++++
.../java/contract/core/apt/SourcePreprocessor.java | 122 ++++
.../java/contract/core/apt/SuperCallBuilder.java | 95 ++++
.../google/java/contract/core/apt/TypeBuilder.java | 444 +++++++++++++++
.../google/java/contract/core/apt/TypeFactory.java | 79 +++
.../google/java/contract/core/model/ClassName.java | 311 ++++++++++
.../core/model/ContractAnnotationModel.java | 223 ++++++++
.../java/contract/core/model/ContractKind.java | 249 ++++++++
.../contract/core/model/ContractMethodModel.java | 243 ++++++++
.../java/contract/core/model/ContractVariance.java | 42 ++
.../java/contract/core/model/ElementKind.java | 195 +++++++
.../java/contract/core/model/ElementModel.java | 272 +++++++++
.../java/contract/core/model/ElementModifier.java | 189 ++++++
.../java/contract/core/model/ElementVisitor.java | 59 ++
.../contract/core/model/GenericElementModel.java | 90 +++
.../java/contract/core/model/HelperTypeModel.java | 76 +++
.../java/contract/core/model/MethodModel.java | 229 ++++++++
.../contract/core/model/QualifiedElementModel.java | 108 ++++
.../google/java/contract/core/model/TypeModel.java | 276 +++++++++
.../google/java/contract/core/model/TypeName.java | 62 ++
.../java/contract/core/model/VariableModel.java | 84 +++
.../contract/core/runtime/BlacklistManager.java | 75 +++
.../contract/core/runtime/ContractContext.java | 113 ++++
.../contract/core/runtime/ContractRuntime.java | 61 ++
.../core/runtime/RuntimeContractEnvironment.java | 108 ++++
.../java/contract/core/util/BalancedTokenizer.java | 146 +++++
.../google/java/contract/core/util/DebugUtils.java | 138 +++++
.../java/contract/core/util/ElementScanner.java | 75 +++
.../google/java/contract/core/util/Elements.java | 132 +++++
.../contract/core/util/EmptyElementVisitor.java | 53 ++
.../java/contract/core/util/JavaTokenizer.java | 312 ++++++++++
.../google/java/contract/core/util/JavaUtils.java | 556 ++++++++++++++++++
.../contract/core/util/LineNumberingTokenizer.java | 72 +++
.../google/java/contract/core/util/PatternMap.java | 218 +++++++
.../java/contract/core/util/PushbackTokenizer.java | 116 ++++
.../java/contract/core/util/SyntheticJavaFile.java | 91 +++
src/com/google/java/contract/util/Iterables.java | 72 +++
src/com/google/java/contract/util/Objects.java | 40 ++
src/com/google/java/contract/util/Predicate.java | 35 ++
src/com/google/java/contract/util/Predicates.java | 327 +++++++++++
.../java/contract/examples/ArrayListStack.java | 60 ++
test/com/google/java/contract/examples/Stack.java | 65 +++
test/com/google/java/contract/tests/Cofoja.java | 33 ++
.../java/contract/tests/ConstantContracts.java | 70 +++
.../java/contract/tests/ConstantContractsTest.java | 94 +++
.../java/contract/tests/ConstructorTest.java | 160 ++++++
.../contract/tests/ContractedAnnotationTest.java | 113 ++++
.../java/contract/tests/ContractedClass.java | 27 +
.../google/java/contract/tests/EmptyContracts.java | 39 ++
.../contract/tests/EnclosedExtendsEnclosing.java | 36 ++
.../tests/EnclosedExtendsEnclosingNoContracts.java | 33 ++
.../tests/EnclosedExtendsEnclosingTest.java | 50 ++
test/com/google/java/contract/tests/EnumTest.java | 70 +++
.../contract/tests/ExceptionInPredicateTest.java | 56 ++
.../java/contract/tests/FinalFieldsTest.java | 119 ++++
.../google/java/contract/tests/GenericsTest.java | 204 +++++++
.../java/contract/tests/InheritanceTest.java | 296 ++++++++++
.../java/contract/tests/InnerAnnotationTest.java | 45 ++
.../java/contract/tests/MemberContractsTest.java | 168 ++++++
.../java/contract/tests/NestedClassTest.java | 120 ++++
.../google/java/contract/tests/PatternMapTest.java | 90 +++
.../google/java/contract/tests/PublicCallTest.java | 113 ++++
.../google/java/contract/tests/ReturnTypeTest.java | 151 +++++
.../contract/tests/SelectiveContractsTest.java | 160 ++++++
.../contract/tests/SeparateGenericSuperclass.java | 34 ++
.../tests/SeparateGenericSuperclassTest.java | 48 ++
.../java/contract/tests/SeparateInterface.java | 40 ++
.../java/contract/tests/SeparateInterfaceTest.java | 105 ++++
.../tests/SeparateInvariantSuperclass.java | 38 ++
.../tests/SeparateInvariantSuperclassTest.java | 54 ++
.../tests/SeparateMethodContractSuperclass.java | 31 +
.../SeparateMethodContractSuperclassTest.java | 54 ++
.../google/java/contract/tests/SimpleMathTest.java | 208 +++++++
test/com/google/java/contract/tests/StackTest.java | 190 +++++++
.../google/java/contract/tests/VariadicTest.java | 118 ++++
.../google/java/contract/tests/selective/a/A.java | 24 +
.../java/contract/tests/selective/a/x/X.java | 24 +
.../java/contract/tests/selective/a/x/X1.java | 24 +
.../google/java/contract/tests/selective/b/B.java | 24 +
.../java/contract/tests/selective/b/y/Y.java | 24 +
134 files changed, 18593 insertions(+)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3462706
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..29adefa
--- /dev/null
+++ b/README
@@ -0,0 +1,136 @@
+Contracts for Java is a contract programming framework for Java, which
+uses annotation processing and bytecode instrumentation to provide
+run-time checking.
+
+
+1. DEPENDENCIES
+
+Contracts fo Java requires Java version 6 for annotation processing
+and bytecode instrumentation. The bytecode rewriter depends on the ASM
+bytecode manipulation library, version 4.x or higher.
+
+Since the Java agent rewrites bytecode in order to inject contracts
+into the loaded code, the ASM library needs to be available in the
+class path at run time if contract checking is enforced through the
+agent. Pre-contracted class files do not need the ASM library or the
+agent to run with contracts enabled.
+
+
+2. CONFIGURATION
+
+In order for the build script to run properly, you must at least
+specify the correct path to Cofoja's dependencies in your local build
+properties file:
+
+ ./local.properties
+
+This file does not exist by default; you can either create it or start
+from a copy of the default configuration file:
+
+ ./default.properties
+
+That file also contains all user-settable properties with their
+descriptions and default values. Once you're done, set the following
+property to true to let Ant run:
+
+ configured=true
+
+
+3. QUICK START
+
+To build a JAR file containing all Cofoja classes, run:
+
+ ant dist
+
+The JAR file will be located at:
+
+ ./dist/lib/cofoja-<version>.jar
+
+It can be used both as a Java agent and annotation processor and
+should be added to your class path.
+
+To compile code with contract annotations, run:
+
+ javac -processor com.google.java.contract.core.apt.AnnotationProcessor <someclass>.java
+
+To execute code compiled with contract checking enabled, make sure the
+generated files (additional .class and .contracts files) are in your
+class path, and run:
+
+ java -javaagent:path/to/cofoja-<version>.jar <someclass>
+
+
+4. ADVANCED BUILD
+
+Contracts for Java is annotated with its own contracts, which can be
+compiled, tested and bundled into the result JAR file so it checks its
+own contracts when compiling and checking your program's contracts!
+
+Please note that running such a build will necessarily be slower than
+running an unchecked version of Contracts for Java, but is a great way
+for you to contribute to the project by helping exercise its own
+capabilities while using it.
+
+To build a contracted version of Contracts for Java, you need to have
+a Cofoja JAR file ready first (see previous section; or you could
+reuse one you've built with a previous run of this bootstrap
+process). Copy the JAR file to:
+
+ ./build/bootstrap.jar
+
+Then run:
+
+ ant bootstrap
+
+Once the contracted version is complete, you can run the test suite
+with:
+
+ ant test
+
+Aside from self-contracted builds, Cofoja JAR files bundled with ASM
+library classes can also be produced, for the sake of easier
+distribution:
+
+ ant -Dasmjar=path/to/asm-all-<version>.jar dist
+
+
+5. USAGE
+
+Contracts for Java consists of an annotation processor, an
+instrumentation agent, as well as an offline bytecode rewriter. The
+annotation processor compiles annotations into separate contract class
+files. The instrumentation agent weaves these contract files with the
+real classes before they are loaded into the JVM. Alternatively, the
+offline bytecode rewriter can be used to produce pre-weaved class
+files that can be directly deployed without any Cofoja dependency.
+
+The following instructions assume that you have the Cofoja and ASM JAR
+files in your class path.
+
+The annotation processor's entry point is (for use with the -processor
+javac option):
+
+ com.google.java.contract.core.apt.AnnotationProcessor
+
+The Java agent can be invoked from the compiled JAR file (for use with
+the -javaagent java option):
+
+ ./dist/cofoja-<version>.jar
+
+The offline instrumenter can be run with:
+
+ java -Dcom.google.java.contract.classoutput=<outdir> \
+ com.google.java.contract.core.agent.PreMain <someclass>.class
+
+Please refer to the official online documentation for more
+information:
+
+ http://code.google.com/p/cofoja/wiki/QuickReference
+
+
+6. BUGS
+
+Contracts for Java is a very young project. Please help us make it
+better by reporting bugs and posting patches at:
+
+ http://code.google.com/p/cofoja/issues/
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..9edd215
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,257 @@
+<project name="Cofoja" default="dist" basedir=".">
+ <!-- Configurable properties. -->
+ <property file="local.properties" />
+ <property file="default.properties" />
+
+ <!-- Private properties. -->
+
+ <property name="src.dir" location="src" />
+ <property name="test.dir" location="test" />
+ <property name="build.dir" location="build" />
+ <property name="obj.dir" location="obj" />
+ <property name="dist.dir" location="dist" />
+
+ <property name="manifest.path"
+ value="${src.dir}/META-INF/MANIFEST.MF" />
+ <property name="apt.class"
+ value="com.google.java.contract.core.apt.AnnotationProcessor" />
+ <property name="test.configurator.class"
+ value="com.google.java.contract.tests.Cofoja" />
+
+ <path id="base.class.path">
+ <pathelement path="${asm.jar}" />
+ <pathelement path="${tools.jar}" />
+ </path>
+
+ <path id="test.class.path">
+ <path refid="base.class.path" />
+ <pathelement path="${junit.jar}" />
+ </path>
+
+ <!-- Build macros. -->
+
+ <presetdef name="ujavac">
+ <javac encoding="utf-8" debug="${debug}" includeantruntime="false" />
+ </presetdef>
+
+ <macrodef name="checkjar">
+ <attribute name="label" />
+ <attribute name="property" />
+ <sequential>
+ <condition property="@{property}.notfound"
+ value="no such file: "
+ else="">
+ <not>
+ <available file="${@{property}}" />
+ </not>
+ </condition>
+ <echo message="@{label}${@{property}.notfound}${@{property}}" />
+ </sequential>
+ </macrodef>
+
+ <macrodef name="requirejar">
+ <attribute name="property" />
+ <sequential>
+ <fail message="Cannot find '${@{property}}'. Please link to the appropriate file or set the '@{property}' property to suit your environment. Consult the 'README' file for more information.">
+ <condition>
+ <not>
+ <equals arg1="${@{property}.notfound}" arg2="" />
+ </not>
+ </condition>
+ </fail>
+ </sequential>
+ </macrodef>
+
+ <macrodef name="barejar">
+ <attribute name="jarfile" />
+ <attribute name="basedir" />
+ <sequential>
+ <jar jarfile="@{jarfile}"
+ basedir="@{basedir}"
+ manifest="${manifest.path}">
+ <service type="javax.annotation.processing.Processor"
+ provider="${apt.class}" />
+ </jar>
+ </sequential>
+ </macrodef>
+
+ <macrodef name="fulljar">
+ <attribute name="jarfile" />
+ <attribute name="barejarfile" />
+ <sequential>
+ <jar jarfile="@{jarfile}"
+ manifest="${manifest.path}">
+ <service type="javax.annotation.processing.Processor"
+ provider="${apt.class}" />
+ <zipfileset includes="**/*.class"
+ src="@{barejarfile}" />
+ <zipfileset includes="**/*.class" src="${asm.jar}" />
+ </jar>
+ </sequential>
+ </macrodef>
+
+ <macrodef name="bootstrapcomp">
+ <attribute name="stage" />
+ <attribute name="bootstrappath" />
+ <sequential>
+ <mkdir dir="${obj.dir}/tmp@{stage}" />
+ <mkdir dir="${obj.dir}/stage@{stage}" />
+ <ujavac srcdir="${src.dir}" destdir="${obj.dir}/tmp@{stage}" debug="true">
+ <classpath refid="base.class.path" />
+ <compilerarg value="-processorpath" />
+ <compilerarg value="@{bootstrappath}:${asm.jar}" />
+ <compilerarg value="-processor" />
+ <compilerarg value="${apt.class}" />
+ </ujavac>
+ <cofojab srcdir="${obj.dir}/tmp@{stage}"
+ destdir="${obj.dir}/stage@{stage}" />
+ </sequential>
+ </macrodef>
+
+ <!-- Initialization. -->
+
+ <target name="configure">
+ <echo message="Configuration" />
+ <echo message="-------------" />
+ <checkjar label="ASM JAR: " property="asm.jar" />
+ <checkjar label="JUnit JAR: " property="junit.jar" />
+ <checkjar label="JDK Tools JAR: " property="tools.jar" />
+ <checkjar label="Bootstrap JAR: " property="bootstrap.jar" />
+ <echo message="Snapshot: ${snapshot}" />
+ <echo message="Debug: ${debug}" />
+ </target>
+
+ <target name="init" depends="configure">
+ <requirejar property="asm.jar" />
+ <requirejar property="tools.jar" />
+
+ <tstamp />
+ <condition property="cofoja.version"
+ value="${version}-${DSTAMP}"
+ else="${version}">
+ <equals arg1="${snapshot}" arg2="true" />
+ </condition>
+ <property name="cofoja.jar"
+ value="${dist.dir}/cofoja-${cofoja.version}.jar" />
+ <property name="cofoja.bare.jar"
+ value="${dist.dir}/cofoja-${cofoja.version}-bare.jar" />
+ <property name="cofoja.contracted.jar"
+ value="${dist.dir}/ccofoja-${cofoja.version}.jar" />
+ <property name="cofoja.contracted.bare.jar"
+ value="${dist.dir}/ccofoja-${cofoja.version}-bare.jar" />
+
+ <mkdir dir="${obj.dir}" />
+ <mkdir dir="${dist.dir}" />
+ </target>
+
+ <!-- Simple build. -->
+
+ <target name="build" depends="init"
+ description="build class files">
+ <mkdir dir="${obj.dir}/bare" />
+ <ujavac srcdir="${src.dir}" destdir="${obj.dir}/bare">
+ <classpath refid="base.class.path" />
+ </ujavac>
+ </target>
+
+ <target name="dist" depends="build"
+ description="build JAR files for distribution">
+ <barejar jarfile="${cofoja.bare.jar}" basedir="${obj.dir}/bare" />
+ <fulljar jarfile="${cofoja.jar}" barejarfile="${cofoja.bare.jar}" />
+ </target>
+
+ <!-- Bootstrap. -->
+
+ <target name="antinit" depends="init">
+ <requirejar property="bootstrap.jar" />
+ <mkdir dir="${obj.dir}/build" />
+ <ujavac srcdir="${build.dir}" destdir="${obj.dir}/build">
+ <classpath>
+ <path refid="base.class.path" />
+ <pathelement path="${java.class.path}" />
+ <pathelement path="${bootstrap.jar}" />
+ </classpath>
+ </ujavac>
+ <taskdef name="cofojab" classname="PreAgentAntTask"
+ classpath="${obj.dir}/build:${bootstrap.jar}:${asm.jar}" />
+ </target>
+
+ <target name="stage0" depends="antinit">
+ <bootstrapcomp stage="0" bootstrappath="${bootstrap.jar}" />
+ </target>
+
+ <target name="stage1" depends="stage0">
+ <bootstrapcomp stage="1" bootstrappath="${obj.dir}/stage0" />
+ </target>
+
+ <target name="stage2" depends="stage1">
+ <bootstrapcomp stage="2" bootstrappath="${obj.dir}/stage1" />
+ </target>
+
+ <target name="bootstrap" depends="stage2"
+ description="build bootstrap-contracted JAR files">
+ <barejar jarfile="${cofoja.contracted.bare.jar}"
+ basedir="${obj.dir}/stage2" />
+ <fulljar jarfile="${cofoja.contracted.jar}"
+ barejarfile="${cofoja.contracted.bare.jar}" />
+ </target>
+
+ <!-- Tests. -->
+
+ <target name="buildtest1" depends="stage2">
+ <requirejar property="junit.jar" />
+ <mkdir dir="${obj.dir}/test" />
+ <ujavac srcdir="${test.dir}" destdir="${obj.dir}/test">
+ <classpath>
+ <path refid="test.class.path" />
+ <pathelement path="${obj.dir}/stage2" />
+ </classpath>
+ <compilerarg value="-processor" />
+ <compilerarg value="${apt.class}" />
+ <include name="**/SeparateGenericSuperclass.java" />
+ <include name="**/SeparateInterface.java" />
+ </ujavac>
+ </target>
+
+ <target name="buildtest2" depends="buildtest1,stage2">
+ <ujavac srcdir="${test.dir}" destdir="${obj.dir}/test">
+ <classpath>
+ <path refid="test.class.path" />
+ <pathelement path="${obj.dir}/stage2" />
+ <pathelement path="${obj.dir}/test" />
+ </classpath>
+ <compilerarg value="-processor" />
+ <compilerarg value="${apt.class}" />
+ <compilerarg value="-Acom.google.java.contract.dump=${obj.dir}/test/dump" />
+ <exclude name="**/SeparateGenericSuperclass.java" />
+ <exclude name="**/SeparateInterface.java" />
+ </ujavac>
+ </target>
+
+ <target name="test" depends="buildtest2,bootstrap"
+ description="run tests">
+ <junit printsummary="yes" haltonfailure="yes">
+ <classpath>
+ <path refid="test.class.path" />
+ <pathelement path="${obj.dir}/stage2" />
+ <pathelement location="${obj.dir}/test"/>
+ </classpath>
+ <jvmarg value="-javaagent:${cofoja.contracted.bare.jar}" />
+ <jvmarg value="-Dcom.google.java.contract.configurator=${test.configurator.class}" />
+ <formatter type="plain" />
+ <batchtest fork="yes" todir="${obj.dir}/test">
+ <fileset dir="${obj.dir}/test">
+ <include name="**/*Test.class"/>
+ </fileset>
+ </batchtest>
+ </junit>
+ </target>
+
+ <!-- Book-keeping. -->
+
+ <target name="clean"
+ description="remove generated files">
+ <delete dir="${obj.dir}" />
+ <delete dir="${dist.dir}" />
+ </target>
+</project>
diff --git a/build/PreAgentAntTask.java b/build/PreAgentAntTask.java
new file mode 100644
index 0000000..bdd3e86
--- /dev/null
+++ b/build/PreAgentAntTask.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+import com.google.java.contract.core.agent.PreMain;
+import com.google.java.contract.core.util.JavaUtils;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.taskdefs.MatchingTask;
+
+import java.io.File;
+import java.util.ArrayList;
+import javax.tools.JavaFileObject.Kind;
+
+public class PreAgentAntTask extends MatchingTask {
+ protected File srcdir;
+ protected File destdir;
+
+ public void setSrcdir(File srcdir) {
+ this.srcdir = srcdir;
+ }
+
+ public void setDestdir(File destdir) {
+ this.destdir = destdir;
+ }
+
+ public void execute() throws BuildException {
+ if (srcdir == null) {
+ throw new BuildException("missing required attribute \"srcdir\"");
+ }
+ if (destdir == null) {
+ throw new BuildException("missing required attribute \"destdir\"");
+ }
+
+ DirectoryScanner ds = getDirectoryScanner(srcdir);
+ try {
+ String[] srcs = ds.getIncludedFiles();
+ ArrayList<String> absSrcs = new ArrayList<String>();
+
+ for (String src : srcs) {
+ if (!src.toString().endsWith(Kind.CLASS.extension)) {
+ continue;
+ }
+ File srcFile = new File(srcdir + "/" + src);
+ File destFile = new File(destdir + "/" + src);
+ if (srcFile.lastModified() > destFile.lastModified()) {
+ absSrcs.add(srcFile.toString());
+ }
+ }
+
+ int n = absSrcs.size();
+ if (n > 0) {
+ System.out.println("Instrumenting " + n
+ + " class file" + (n == 1 ? "" : "s")
+ + " to " + destdir);
+ ClassLoader loader =
+ JavaUtils.getLoaderForPath(srcdir.toString(),
+ getClass().getClassLoader());
+ PreMain.instrument(absSrcs.toArray(new String[0]),
+ destdir.toString(),
+ loader);
+ }
+ } catch (Exception e) {
+ throw new BuildException(e);
+ }
+ }
+}
diff --git a/default.properties b/default.properties
new file mode 100644
index 0000000..a74d238
--- /dev/null
+++ b/default.properties
@@ -0,0 +1,23 @@
+## You can override the following properties by creating
+## a 'local.properties' configuration file.
+
+## Version information. Set 'snapshot' to false if you are building
+## a release.
+version=1.1
+snapshot=true
+debug=true
+
+## Location of an older build of Cofoja, for bootstrapping purposes.
+bootstrap.jar=build/bootstrap.jar
+
+## Location of dependency JAR files needed to build Cofoja. The
+## defaults assume the JAR files are in the 'build' subdirectory,
+## except for 'tools.jar', which is probably not what you want.
+asm.jar=build/asm.jar
+junit.jar=build/junit.jar
+tools.jar=${java.home}/../lib/tools.jar
+
+## If you use Maven, the following may help, instead.
+# maven.repo.dir=${user.home}/.m2/repository
+# asm.jar=${maven.repo.dir}/asm/asm-all/3.3.1/asm-all-3.3.1.jar
+# junit.jar=${maven.repo.dir}/junit/junit/4.8.2/junit-4.8.2.jar
diff --git a/script/cofojab.sh b/script/cofojab.sh
new file mode 100755
index 0000000..384457d
--- /dev/null
+++ b/script/cofojab.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+# Copyright 2010 Google Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+usage()
+{
+ cat <<EOF
+Usage: ${0##*/} [<options>] <file>[.class] ...
+Options:
+ -d <dir> output class directory; if not specified, output
+ class files are suffixed with .contracted
+ -java <java> calls this instead of java
+ -jar <file> specifies the JAR file that contains Contracts for Java
+ -configurator <class> instantiates and calls this configurator
+ <file>[.class] the class files to instrument; helper class
+ files need not be specified explicitly
+EOF
+ exit
+}
+
+shellquote()
+{
+ echo "$1" | sed "s/'/'\\\\''/g"
+}
+
+JAVA=${JAVA:-java}
+
+configurator=
+jarfile=
+classoutput=
+files=
+
+if [ $# -eq 0 ]; then
+ usage
+fi
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -configurator)
+ shift
+ [ $# -eq 0 ] && usage
+ configurator="$1"
+ ;;
+ -d)
+ shift
+ [ $# -eq 0 ] && usage
+ classoutput="$1"
+ ;;
+ -jar)
+ shift
+ [ $# -eq 0 ] && usage
+ jarfile="$1"
+ ;;
+ -java)
+ shift
+ [ $# -eq 0 ] && usage
+ JAVA="$1"
+ ;;
+ *)
+ files="$files '$(shellquote "$1")'"
+ esac
+ shift
+done
+
+cmd=$JAVA
+if [ "$configurator" ]; then
+ cmd="$cmd -Dcom.google.java.contract.configurator=\"$configurator\""
+fi
+if [ "$jarfile" ]; then
+ if [ "$CLASSPATH" ]; then
+ export CLASSPATH=$jarfile:$CLASSPATH
+ else
+ export CLASSPATH=$jarfile:.
+ fi
+fi
+if [ "$classoutput" ]; then
+ cmd="$cmd -Dcom.google.java.contract.classoutput=\"$classoutput\""
+fi
+cmd="$cmd com.google.java.contract.core.agent.PreMain $files"
+
+eval "$cmd" || exit
diff --git a/script/cofojac.sh b/script/cofojac.sh
new file mode 100755
index 0000000..e7ede31
--- /dev/null
+++ b/script/cofojac.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+# Copyright 2010 Google Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+usage()
+{
+ cat <<EOF
+Usage: ${0##*/} [<options>] [<javac-option>] ... <source> ...
+Options:
+ -java <javac> calls this instead of java
+ -javac <javac> calls this instead of javac
+ -g:contracts compiles debug contract code
+ -deps:none skip source dependency preprocessing
+ -deps:only stop after source dependency preprocessing
+ <javac-option> passes argument to javac
+ <source> source file
+EOF
+ exit
+}
+
+shellquote()
+{
+ echo "$1" | sed "s/'/'\\\\''/g"
+}
+
+JAVA=${JAVA:-java}
+JAVAC=${JAVAC:-javac}
+
+classpath=
+classoutput=
+debug=no
+passthrough=
+
+procapt=yes
+procdeps=yes
+
+if [ $# -eq 0 ]; then
+ usage
+fi
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -cp|-classpath)
+ shift
+ [ $# -eq 0 ] && usage
+ classpath="$1"
+ ;;
+ -d)
+ shift
+ [ $# -eq 0 ] && usage
+ classoutput="$1"
+ ;;
+ -deps:only)
+ procdeps=yes
+ procapt=no
+ ;;
+ -deps:none)
+ procdeps=no
+ ;;
+ -g:contracts)
+ debug=yes
+ ;;
+ -java)
+ shift
+ [ $# -eq 0 ] && usage
+ JAVA="$1"
+ ;;
+ -javac)
+ shift
+ [ $# -eq 0 ] && usage
+ JAVAC="$1"
+ ;;
+ *)
+ passthrough="$passthrough '$(shellquote "$1")'"
+ esac
+ shift
+done
+
+depsdir=
+if [ $procdeps = yes ]; then
+ cmd=$JAVA
+ if [ $procapt = yes ]; then
+ depsdir=$$.com.google.java.contract.d
+ mkdir "$depsdir"
+ cmd="$cmd -Dcom.google.java.contract.depsoutput=\"\$depsdir\""
+ elif [ "$classoutput" ]; then
+ cmd="$cmd -Dcom.google.java.contract.depsoutput=\"\$classoutput\""
+ fi
+ cmd="$cmd com.google.java.contract.core.apt.SourcePreprocessor $passthrough"
+
+ eval "$cmd" || exit
+fi
+
+if [ $procapt = yes ]; then
+ cmd=$JAVAC
+ if [ "$classpath" ]; then
+ cmd="$cmd -cp \"\$classpath\" -Acom.google.java.contract.classpath=\"\$classpath\""
+ fi
+ if [ "$classoutput" ]; then
+ cmd="$cmd -d \"\$classoutput\" -Acom.google.java.contract.classoutput=\"\$classoutput\""
+ fi
+ if [ $debug = yes ]; then
+ cmd="$cmd -Acom.google.java.contract.debug"
+ fi
+ if [ "$depsdir" ]; then
+ cmd="$cmd -Acom.google.java.contract.depspath=\"\$depsdir\""
+ fi
+ cmd="$cmd $passthrough"
+
+ eval "$cmd"
+ excode=$?
+
+ if [ "$depsdir" ]; then
+ rm -rf "$depsdir"
+ fi
+
+ if [ $excode -ne 0 ]; then
+ exit $excode
+ fi
+fi
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..f255494
--- /dev/null
+++ b/src/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Premain-Class: com.google.java.contract.core.agent.PreMain
diff --git a/src/META-INF/services/javax.annotation.processing.Processor b/src/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..76ff481
--- /dev/null
+++ b/src/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+com.google.java.contract.core.apt.AnnotationProcessor
diff --git a/src/com/google/java/contract/AllowUnusedImport.java b/src/com/google/java/contract/AllowUnusedImport.java
new file mode 100644
index 0000000..ab4191b
--- /dev/null
+++ b/src/com/google/java/contract/AllowUnusedImport.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * References a class so it is considered used by import statement
+ * cleaning tools. Such a class may be used only in contracts, which
+ * are not understood by such tools.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.SOURCE)
+public @interface AllowUnusedImport {
+ /**
+ * The list of classes to suppress import warnings for.
+ */
+ Class<?>[] value();
+}
diff --git a/src/com/google/java/contract/ContractAssertionError.java b/src/com/google/java/contract/ContractAssertionError.java
new file mode 100644
index 0000000..6e87bd5
--- /dev/null
+++ b/src/com/google/java/contract/ContractAssertionError.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for contract assertion errors. You should generally not
+ * catch this.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public abstract class ContractAssertionError extends AssertionError {
+ /**
+ * Constructs a new ContractAssertionError.
+ *
+ * @param msg the error message.
+ */
+ public ContractAssertionError(String msg) {
+ super(msg);
+ cleanStackTrace();
+ }
+
+ /**
+ * Constructs a new ContractAssertionError.
+ *
+ * @param msg the error message.
+ * @param throwable the throwable caught while evaluating contracts, or null
+ * for none.
+ */
+ public ContractAssertionError(String msg, Throwable throwable) {
+ this(
+ throwable == null
+ ? msg
+ : "evaluating \"" + msg + "\" caused " + throwable.getClass().getSimpleName());
+ }
+
+ /**
+ * Constructs a new ContractAssertionError.
+ *
+ * @param msg the error message.
+ * @param cause a previous contract error
+ */
+ public ContractAssertionError(String msg, ContractAssertionError cause) {
+ super(msg);
+ initCause(cause);
+ cleanStackTrace();
+ }
+
+ /**
+ * Constructs a new ContractAssertionError.
+ *
+ * @param msg the error message.
+ * @param cause a previous contract error
+ * @param throwable the throwable caught while evaluating contracts, or null
+ * for none.
+ */
+ public ContractAssertionError(String msg, ContractAssertionError cause, Throwable throwable) {
+ this(
+ throwable == null
+ ? msg
+ : "evaluating \"" + msg + "\" caused " + throwable.getClass().getSimpleName(),
+ cause);
+ }
+
+ /**
+ * Remove wrapper call, leaving only the contract helper.
+ */
+ private void cleanStackTrace() {
+ StackTraceElement[] realTrace = getStackTrace();
+ StackTraceElement[] trace = new StackTraceElement[realTrace.length - 1];
+ StackTraceElement top = realTrace[0];
+ trace[0] = new StackTraceElement(top.getClassName(),
+ getMethodName(realTrace[2].getMethodName()),
+ top.getFileName(), top.getLineNumber());
+ System.arraycopy(realTrace, 2, trace, 1, realTrace.length - 2);
+ setStackTrace(trace);
+ }
+
+ public List<String> getMessages() {
+ ArrayList<String> list = new ArrayList<String>();
+ Throwable error = this;
+ do {
+ list.add(error.getMessage());
+ error = error.getCause();
+ } while (error != null);
+ return list;
+ }
+
+ /**
+ * Returns the method name to show in the stack trace instead of the
+ * generated method name for the contract.
+ */
+ protected abstract String getMethodName(String contractedName);
+}
diff --git a/src/com/google/java/contract/ContractEnvironment.java b/src/com/google/java/contract/ContractEnvironment.java
new file mode 100644
index 0000000..bca925f
--- /dev/null
+++ b/src/com/google/java/contract/ContractEnvironment.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An object that exposes methods to alter the contracting
+ * environment. Any changes made to the environment are only
+ * guaranteed to take effect on future actions; for example, disabling
+ * contracts on an already loaded class has no effect.
+ *
+ * <p>Methods that match multiple classes accept patterns. Patterns
+ * are semi-qualified names (nested classes have their names
+ * flattened), optionally followed by {@code .*}. A normal pattern
+ * matches itself exactly. A star pattern matches any class whose name
+ * begins with the pattern minus the terminating {@code .*}. In case
+ * of pattern overlap, subsequent rules override previous ones.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public interface ContractEnvironment {
+ /**
+ * Enables precondition checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("hasPreconditionsEnabled(pattern)")
+ public void enablePreconditions(String pattern);
+
+ /**
+ * Disables precondition checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("!hasPreconditionsEnabled(pattern)")
+ public void disablePreconditions(String pattern);
+
+ /**
+ * Enables postcondition checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("hasPostconditionsEnabled(pattern)")
+ public void enablePostconditions(String pattern);
+
+ /**
+ * Disables postcondition checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("!hasPostconditionsEnabled(pattern)")
+ public void disablePostconditions(String pattern);
+
+ /**
+ * Enables invariant checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("hasInvariantsEnabled(pattern)")
+ public void enableInvariants(String pattern);
+
+ /**
+ * Disables invariant checking for classes matched by pattern
+ * {@code pattern}.
+ *
+ * @throws UnsupportedOperationException if this environment does
+ * not support selective contract activation
+ */
+ @Requires("pattern != null")
+ @Ensures("!hasInvariantsEnabled(pattern)")
+ public void disableInvariants(String pattern);
+
+ /**
+ * Returns {@code true} if {@code clazz} has preconditions
+ * enabled. This does <em>not</em> imply that such a class has any
+ * preconditions at all.
+ */
+ @Requires("clazz != null")
+ public boolean hasPreconditionsEnabled(Class<?> clazz);
+
+ /**
+ * Returns {@code true} if all classes matched by {@code pattern}
+ * have preconditions enabled. This does <em>not</em> imply that
+ * such a class has any preconditions at all.
+ */
+ @Requires("pattern != null")
+ public boolean hasPreconditionsEnabled(String pattern);
+
+ /**
+ * Returns {@code true} if {@code clazz} has postconditions
+ * enabled. This does <em>not</em> imply that such a class has any
+ * postconditions at all.
+ */
+ @Requires("clazz != null")
+ public boolean hasPostconditionsEnabled(Class<?> clazz);
+
+ /**
+ * Returns {@code true} if all classes matched by {@code pattern}
+ * have postconditions enabled. This does <em>not</em> imply that
+ * such a class has any postconditions at all.
+ */
+ @Requires("pattern != null")
+ public boolean hasPostconditionsEnabled(String pattern);
+
+ /**
+ * Returns {@code true} if {@code clazz} has invariants
+ * enabled. This does <em>not</em> imply that such a class has any
+ * invariants at all.
+ */
+ @Requires("clazz != null")
+ public boolean hasInvariantsEnabled(Class<?> clazz);
+
+ /**
+ * Returns {@code true} if all classes matched by {@code pattern}
+ * have invariants enabled. This does <em>not</em> imply that such a
+ * class has any invariants at all.
+ */
+ @Requires("pattern != null")
+ public boolean hasInvariantsEnabled(String pattern);
+
+ /**
+ * Ignores classes matched by {@code pattern}. Ignored classes are
+ * not touched by Contracts for Java in any way: they are neither
+ * loaded nor examined for contracts.
+ *
+ * <p>If you are looking for a method to disable contracts, this is
+ * <em>not</em> the right method; use methods such as
+ * {@link #disableInvariants(String)} instead.
+ *
+ * <p>By default, the following classes are ignored:
+ *
+ * <ul>
+ * <li>{@code java.*}
+ * <li>{@code javax.*}
+ * <li>{@code com.sun.*}
+ * <li>{@code sun.*}
+ * </ul>
+ */
+ @Requires("pattern != null")
+ @Ensures("isIgnored(pattern)")
+ public void ignore(String pattern);
+
+ /**
+ * Unignore classes matched by {@code pattern}. This method can be
+ * used to unignore some classes that have previously been ignored,
+ * or that are ignored by default.
+ *
+ * @see #ignore(String)
+ */
+ @Requires("pattern != null")
+ @Ensures("!isIgnored(pattern)")
+ public void unignore(String pattern);
+
+ /**
+ * Returns {@code true} if all classes matched by {@code pattern}
+ * are ignored.
+ *
+ * @see #ignore(String)
+ */
+ @Requires("pattern != null")
+ public boolean isIgnored(String pattern);
+}
diff --git a/src/com/google/java/contract/ContractEnvironmentConfigurator.java b/src/com/google/java/contract/ContractEnvironmentConfigurator.java
new file mode 100644
index 0000000..b4137da
--- /dev/null
+++ b/src/com/google/java/contract/ContractEnvironmentConfigurator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An object that can be called by Contracts for Java to configure the
+ * contract environment.
+ *
+ * <p>As the last step of its startup procedure, Contracts for Java
+ * instantiates a new object of the class specified by the system
+ * property {@code com.google.java.contract.configurator} and calls the method
+ * {@link #configure(ContractEnvironment)} on it.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public interface ContractEnvironmentConfigurator {
+ /**
+ * Configures the contract environment. It is safe for this method
+ * to store {@code contractEnv} for later use, if needed.
+ */
+ @Requires("contractEnv != null")
+ public void configure(ContractEnvironment contractEnv);
+}
diff --git a/src/com/google/java/contract/Ensures.java b/src/com/google/java/contract/Ensures.java
new file mode 100644
index 0000000..a704c58
--- /dev/null
+++ b/src/com/google/java/contract/Ensures.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies postconditions that apply to the annotated method. The
+ * annotated method must establish its postconditions if and only if
+ * the preconditions were satisfied.
+ *
+ * <p>When run time checking of contracts is enabled, postconditions
+ * are checked at method exit, when the method exits normally, of the
+ * and throw a {@link com.google.java.contract.PreconditionError} when they
+ * are violated. Postconditions are not checked when the method exits
+ * by throwing an exception.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @see ThrowEnsures
+ */
+ at Documented
+ at Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Ensures {
+ /**
+ * The list of postconditions that must be met by the annotated
+ * method, written as strings. The expressions must be valid Java
+ * code and can reference all things visible to the class, as well
+ * as the method's arguments. Since postconditions may need to refer
+ * to the old value of an expression and the value returned by the
+ * annotated method, the following extensions are allowed:
+ *
+ * <p>The keyword {@code result} refers to the value returned from
+ * the method, if any. It is an error to have a method parameter
+ * named {@code result}.
+ *
+ * <p>The {@code old(expression)} pseudo-method construct refers to
+ * the value of its argument before execution of the method.
+ * {@code expression} must be a balanced expression, with regard to
+ * parentheses. The {@code old()} construct is preprocessed using
+ * the following simple rules (similar to macro expansion done by a
+ * C preprocessor):
+ *
+ * <ul>
+ * <li>the word {@code old}, followed by an opening parenthesis, is
+ * recognized as an identifier;
+ * <li>the enclosed expression is only parsed for balanced
+ * parentheses;
+ * <li>textual substitution is used.
+ * </ul>
+ *
+ * <p>It is an error to call a method named {@code old} from within
+ * a postcondition.
+ */
+ String[] value();
+}
diff --git a/src/com/google/java/contract/Invariant.java b/src/com/google/java/contract/Invariant.java
new file mode 100644
index 0000000..68e17f0
--- /dev/null
+++ b/src/com/google/java/contract/Invariant.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies class invariants that apply to the annotated type. The
+ * annotated class must guarantee its invariants.
+ *
+ * <p>When run time checking of contracts is enabled, class invariants
+ * are checked on entry and exit of public and package-private
+ * methods, and throw a {@link com.google.java.contract.InvariantError} when
+ * they are violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Documented
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Invariant {
+ /**
+ * The list of invariant expressions that must be met by the
+ * annotated type, written as strings. The expressions must be valid
+ * Java code and can reference all things visible to the class,
+ * including private members.
+ */
+ String[] value();
+}
diff --git a/src/com/google/java/contract/InvariantError.java b/src/com/google/java/contract/InvariantError.java
new file mode 100644
index 0000000..041715d
--- /dev/null
+++ b/src/com/google/java/contract/InvariantError.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An exception thrown when a class invariant is violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ * @see com.google.java.contract.Invariant
+ */
+public class InvariantError extends ContractAssertionError {
+ /**
+ * Constructs a new InvariantError.
+ *
+ * @param msg the error message.
+ */
+ public InvariantError(String msg) {
+ super(msg, null);
+ }
+
+ /**
+ * Constructs a new InvariantError.
+ *
+ * @param msg the error message.
+ * @param throwable the throwable caught while evaluating contracts, or null
+ * for none.
+ */
+ public InvariantError(String msg, Throwable throwable) {
+ super(msg, throwable);
+ }
+
+ @Override
+ protected String getMethodName(String contractedName) {
+ return "<invariant>";
+ }
+}
diff --git a/src/com/google/java/contract/PostconditionError.java b/src/com/google/java/contract/PostconditionError.java
new file mode 100644
index 0000000..4016c97
--- /dev/null
+++ b/src/com/google/java/contract/PostconditionError.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An exception thrown when a postcondition is violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ * @see com.google.java.contract.Ensures
+ */
+public class PostconditionError extends ContractAssertionError {
+ /**
+ * Constructs a new PostconditionError.
+ *
+ * @param msg the error message.
+ */
+ public PostconditionError(String msg) {
+ super(msg, null);
+ }
+
+ /**
+ * Constructs a new PostconditionError.
+ *
+ * @param msg the error message.
+ * @param throwable the throwable caught while evaluating contracts, or null
+ * for none.
+ */
+ public PostconditionError(String msg, Throwable throwable) {
+ super(msg, throwable);
+ }
+
+ @Override
+ protected String getMethodName(String contractedName) {
+ return contractedName + ".<post>";
+ }
+}
diff --git a/src/com/google/java/contract/PreconditionError.java b/src/com/google/java/contract/PreconditionError.java
new file mode 100644
index 0000000..b326113
--- /dev/null
+++ b/src/com/google/java/contract/PreconditionError.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An exception thrown when a precondition is violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ * @see com.google.java.contract.Requires
+ */
+public class PreconditionError extends ContractAssertionError {
+ /**
+ * Constructs a new PreconditionError.
+ *
+ * @param msg the error message.
+ * @param cause a previously failing precondition
+ */
+ public PreconditionError(String msg, PreconditionError cause) {
+ super(msg, cause, null);
+ }
+
+ /**
+ * Constructs a new PreconditionError.
+ *
+ * @param msg the error message.
+ * @param cause a previously failing precondition
+ * @param throwable the throwable caught while evaluating contracts, or null
+ * for none.
+ */
+ public PreconditionError(String msg, PreconditionError cause, Throwable throwable) {
+ super(msg, cause, throwable);
+ }
+
+ @Override
+ protected String getMethodName(String contractedName) {
+ return contractedName + ".<pre>";
+ }
+}
diff --git a/src/com/google/java/contract/Requires.java b/src/com/google/java/contract/Requires.java
new file mode 100644
index 0000000..11626e9
--- /dev/null
+++ b/src/com/google/java/contract/Requires.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies preconditions that apply to the annotated method. Callers
+ * must establish the preconditions of methods they call.
+ *
+ * <p>When run time checking of contracts is enabled, preconditions
+ * are checked at method entry and throw a
+ * {@link com.google.java.contract.PreconditionError} when they are violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Documented
+ at Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Requires {
+ /**
+ * The list of precondition expressions that must be met by the
+ * annotated method, written as strings. The expressions must be
+ * valid Java code and can reference all things visible to every
+ * caller of the method, as well as the method's arguments.
+ *
+ * <p>Expressions may also reference things that are not visible
+ * to the caller, such as private fields when the method is public,
+ * but this is considered bad style.
+ */
+ String[] value();
+}
diff --git a/src/com/google/java/contract/SpecificationError.java b/src/com/google/java/contract/SpecificationError.java
new file mode 100644
index 0000000..f8c347a
--- /dev/null
+++ b/src/com/google/java/contract/SpecificationError.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+/**
+ * An error in contract annotation usage.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class SpecificationError extends Error {
+ /**
+ * Constructs a new SpecificationError.
+ *
+ * @param msg the error message.
+ */
+ @Ensures("msg == null || msg.equals(getMessage())")
+ public SpecificationError(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/com/google/java/contract/ThrowEnsures.java b/src/com/google/java/contract/ThrowEnsures.java
new file mode 100644
index 0000000..6166f6f
--- /dev/null
+++ b/src/com/google/java/contract/ThrowEnsures.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies exceptional postconditions that apply to the annotated
+ * method. Exceptional postconditions apply whenever the annotated
+ * method exits by throwing an exception, in contrast to normal
+ * postconditions that apply when the method exits normally. The
+ * annotated method must establish its postconditions if and only if
+ * the preconditions were satisfied.
+ *
+ * <p>When run time checking of contracts is enabled, exceptional
+ * postconditions are checked at method exit, when the method exits by
+ * throwing an exception, and throw a
+ * {@link com.google.java.contract.PostconditionError} when they are violated.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @see Ensures
+ */
+ at Documented
+ at Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface ThrowEnsures {
+ /**
+ * The alternating list of signal--postcondition pairs that must be
+ * met on throw. The expressions must be valid Java code and can
+ * reference all things visible to the class, as well as the
+ * method's arguments. Since exceptional postconditions may need to
+ * refer to the old value of an expression and the exception object
+ * thrown by the annotated method, the following extensions are
+ * allowed:
+ *
+ * <p>The {@code signal} keyword refers to the object thrown
+ * by the method, and has for static type
+ * {@link java.lang.Throwable}.
+ *
+ * <p>The {@code old()} construct has the same syntax and semantics
+ * as in normal postconditions.
+ *
+ * @see Ensures#value()
+ */
+ String[] value();
+}
diff --git a/src/com/google/java/contract/core/agent/ActivationRuleManager.java b/src/com/google/java/contract/core/agent/ActivationRuleManager.java
new file mode 100644
index 0000000..0c6f23b
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ActivationRuleManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.core.util.PatternMap;
+
+/**
+ * A process-wide collection of contract activation rules.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "assertPre != null",
+ "assertPost != null",
+ "assertInvariant != null"
+})
+public class ActivationRuleManager {
+ protected static ActivationRuleManager instance = null;
+
+ protected PatternMap<Boolean> assertPre = new PatternMap<Boolean>();
+ protected PatternMap<Boolean> assertPost = new PatternMap<Boolean>();
+ protected PatternMap<Boolean> assertInvariant = new PatternMap<Boolean>();
+
+ protected ActivationRuleManager() {
+ assertPre = new PatternMap<Boolean>();
+ assertPost = new PatternMap<Boolean>();
+ assertInvariant = new PatternMap<Boolean>();
+ }
+
+ public static ActivationRuleManager getInstance() {
+ if (instance == null) {
+ instance = new ActivationRuleManager();
+ }
+ return instance;
+ }
+
+ @Requires("pattern != null")
+ @Ensures("hasPreconditionsEnabled(pattern)")
+ public synchronized void enablePreconditions(String pattern) {
+ DebugUtils.info("activation", pattern + " +requires");
+ assertPre.put(pattern, true);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("!hasPreconditionsEnabled(pattern)")
+ public synchronized void disablePreconditions(String pattern) {
+ DebugUtils.info("activation", pattern + " -requires");
+ assertPre.put(pattern, false);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("hasPostconditionsEnabled(pattern)")
+ public synchronized void enablePostconditions(String pattern) {
+ DebugUtils.info("activation", pattern + " +ensures");
+ assertPost.put(pattern, true);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("!hasPostconditionsEnabled(pattern)")
+ public synchronized void disablePostconditions(String pattern) {
+ DebugUtils.info("activation", pattern + " -ensures");
+ assertPost.put(pattern, false);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("hasInvariantsEnabled(pattern)")
+ public synchronized void enableInvariants(String pattern) {
+ DebugUtils.info("activation", pattern + " +invariant");
+ assertInvariant.put(pattern, true);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("!hasInvariantsEnabled(pattern)")
+ public synchronized void disableInvariants(String pattern) {
+ DebugUtils.info("activation", pattern + " -invariant");
+ assertInvariant.put(pattern, false);
+ }
+
+ @Requires("pattern != null")
+ public synchronized boolean hasPreconditionsEnabled(String pattern) {
+ if (pattern.endsWith(".*") && assertPre.isOverriden(pattern)) {
+ return false;
+ }
+ Boolean rule = assertPre.get(pattern);
+ return rule == null || rule;
+ }
+
+ @Requires("pattern != null")
+ public synchronized boolean hasPostconditionsEnabled(String pattern) {
+ if (pattern.endsWith(".*") && assertPost.isOverriden(pattern)) {
+ return false;
+ }
+ Boolean rule = assertPost.get(pattern);
+ return rule == null || rule;
+ }
+
+ @Requires("pattern != null")
+ public synchronized boolean hasInvariantsEnabled(String pattern) {
+ if (pattern.endsWith(".*") && assertInvariant.isOverriden(pattern)) {
+ return false;
+ }
+ Boolean rule = assertInvariant.get(pattern);
+ return rule == null || rule;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/AgentContractEnvironment.java b/src/com/google/java/contract/core/agent/AgentContractEnvironment.java
new file mode 100644
index 0000000..5b94b52
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/AgentContractEnvironment.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.core.runtime.RuntimeContractEnvironment;
+
+/**
+ * A contract environment running under the Cofoja Java agent.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("activationManager != null")
+public class AgentContractEnvironment extends RuntimeContractEnvironment {
+ protected ActivationRuleManager activationManager;
+
+ public AgentContractEnvironment() {
+ activationManager = ActivationRuleManager.getInstance();
+ }
+
+ @Override
+ public void enablePreconditions(String pattern) {
+ activationManager.enablePreconditions(pattern);
+ }
+
+ @Override
+ public void disablePreconditions(String pattern) {
+ activationManager.disablePreconditions(pattern);
+ }
+
+ @Override
+ public void enablePostconditions(String pattern) {
+ activationManager.enablePostconditions(pattern);
+ }
+
+ @Override
+ public void disablePostconditions(String pattern) {
+ activationManager.disablePostconditions(pattern);
+ }
+
+ @Override
+ public void enableInvariants(String pattern) {
+ activationManager.enableInvariants(pattern);
+ }
+
+ @Override
+ public void disableInvariants(String pattern) {
+ activationManager.disableInvariants(pattern);
+ }
+
+ @Override
+ public boolean hasPreconditionsEnabled(Class<?> clazz) {
+ return activationManager.hasPreconditionsEnabled(clazz.getName());
+ }
+
+ @Override
+ public boolean hasPreconditionsEnabled(String pattern) {
+ return activationManager.hasPreconditionsEnabled(pattern);
+ }
+
+ @Override
+ public boolean hasPostconditionsEnabled(Class<?> clazz) {
+ return activationManager.hasPostconditionsEnabled(clazz.getName());
+ }
+
+ @Override
+ public boolean hasPostconditionsEnabled(String pattern) {
+ return activationManager.hasPostconditionsEnabled(pattern);
+ }
+
+ @Override
+ public boolean hasInvariantsEnabled(Class<?> clazz) {
+ return activationManager.hasInvariantsEnabled(clazz.getName());
+ }
+
+ @Override
+ public boolean hasInvariantsEnabled(String pattern) {
+ return activationManager.hasInvariantsEnabled(pattern);
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ClassContractHandle.java b/src/com/google/java/contract/core/agent/ClassContractHandle.java
new file mode 100644
index 0000000..212f8d6
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ClassContractHandle.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractKind;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+
+/**
+ * A contract handle representing a contract method with class-wide
+ * effect.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class ClassContractHandle extends ContractHandle {
+ /**
+ * Constructs a new ClassContractHandle.
+ *
+ * @param kind the kind of this ClassContractHandle
+ * @param className the class this handle belongs to
+ * @param contractMethod the {@link MethodNode} representing this
+ * handle's method
+ * @param lineNumbers the line numbers associated with the contract
+ */
+ @Requires({
+ "kind != null",
+ "className != null",
+ "contractMethod != null"
+ })
+ @Ensures({
+ "kind == getKind()",
+ "className.equals(getClassName())",
+ "contractMethod == getContractMethod()",
+ "lineNumbers == getLineNumbers()"
+ })
+ public ClassContractHandle(ContractKind kind, String className,
+ MethodNode contractMethod,
+ List<Long> lineNumbers) {
+ super(kind, className, contractMethod, lineNumbers);
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ContractAnalyzer.java b/src/com/google/java/contract/core/agent/ContractAnalyzer.java
new file mode 100644
index 0000000..931dbe3
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractAnalyzer.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Bytecode class visitor responsible for contract information
+ * extraction.
+ *
+ * After a class has been visited, this visitor exposes the resulting
+ * handles through filtering accessor methods such as
+ * {@link #getClassHandles(ContractKind)}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class })
+ at Invariant({
+ "className == null || ClassName.isBinaryName(className)",
+ "classHandles != null",
+ "!classHandles.contains(null)",
+ "methodHandles != null",
+ "!methodHandles.keySet().contains(null)",
+ "Iterables.all(methodHandles.values(), " +
+ "Predicates.<MethodContractHandle>all(Predicates.nonNull()))"
+})
+class ContractAnalyzer extends ClassVisitor {
+ protected List<ClassContractHandle> classHandles;
+ protected Map<String, ArrayList<MethodContractHandle>> methodHandles;
+
+ protected String className;
+ protected MethodNode lastMethodNode;
+
+ /**
+ * Constructs an empty ContractAnalyzer. The ContractAnalyzer is
+ * intended to be filled through its visitor interface.
+ */
+ ContractAnalyzer() {
+ super(Opcodes.ASM4);
+ classHandles = new ArrayList<ClassContractHandle>();
+ methodHandles = new HashMap<String, ArrayList<MethodContractHandle>>();
+ }
+
+ /**
+ * Returns the ClassHandle objects matching the specified criteria.
+ *
+ * @param kind the kind of the handles
+ * @return a list containing the requested handles
+ */
+ @Requires("kind != null")
+ @Ensures({
+ "result != null",
+ "!result.contains(null)"
+ })
+ List<ClassContractHandle> getClassHandles(ContractKind kind) {
+ ArrayList<ClassContractHandle> matched =
+ new ArrayList<ClassContractHandle>();
+ for (ClassContractHandle h : classHandles) {
+ if (kind.equals(h.getKind())) {
+ matched.add(h);
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns the MethodHandle objects matching the specified criteria.
+ *
+ * @param kind the kind of the handles
+ * @param name the target method name
+ * @param desc the target method descriptor
+ * @param extraCount the number of extra parameters in the contract
+ * method
+ * @return a list containing the requested handles
+ */
+ @Requires({
+ "kind != null",
+ "name != null",
+ "desc != null",
+ "extraCount >= 0"
+ })
+ @Ensures({
+ "result != null",
+ "!result.contains(null)"
+ })
+ List<MethodContractHandle> getMethodHandles(ContractKind kind,
+ String name, String desc, int extraCount) {
+ ArrayList<MethodContractHandle> candidates = methodHandles.get(name);
+ if (candidates == null) {
+ return Collections.emptyList();
+ }
+
+ ArrayList<MethodContractHandle> matched =
+ new ArrayList<MethodContractHandle>();
+ for (MethodContractHandle h : candidates) {
+ if (kind.equals(h.getKind())
+ && descArgumentsMatch(desc, h.getContractMethod().desc, extraCount)) {
+ matched.add(h);
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Returns the first ClassHandle object matching the specified
+ * criteria.
+ *
+ * @param kind the kind of the handle
+ * @return a handle matching the criteria, or {@code null}
+ */
+ @Requires("kind != null")
+ ClassContractHandle getClassHandle(ContractKind kind) {
+ ArrayList<ClassContractHandle> matched =
+ new ArrayList<ClassContractHandle>();
+ for (ClassContractHandle h : classHandles) {
+ if (kind.equals(h.getKind())) {
+ return h;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first MethodHandle object matching the specified
+ * criteria.
+ *
+ * @param kind the kind of the handle
+ * @param name the target method name
+ * @param desc the target method descriptor
+ * @param extraCount the number of extra parameters in the contract
+ * method
+ * @return a handle matching the criteria, or {@code null}
+ */
+ @Requires({
+ "kind != null",
+ "name != null",
+ "desc != null",
+ "extraCount >= 0"
+ })
+ MethodContractHandle getMethodHandle(ContractKind kind,
+ String name, String desc, int extraCount) {
+ ArrayList<MethodContractHandle> candidates = methodHandles.get(name);
+ if (candidates == null) {
+ return null;
+ }
+
+ for (MethodContractHandle h : candidates) {
+ if (kind.equals(h.getKind())
+ && descArgumentsMatch(desc, h.getContractMethod().desc, extraCount)) {
+ return h;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if the argument descriptors {@code desc1}
+ * and {@code desc2} are equal (return type is ignored), ignoring
+ * the last {@code offset} parameters of {@code desc2}.
+ */
+ @Requires({
+ "desc1 != null",
+ "desc2 != null",
+ "offset >= 0"
+ })
+ private boolean descArgumentsMatch(String desc1, String desc2, int offset) {
+ Type[] types1 = Type.getArgumentTypes(desc1);
+ Type[] types2 = Type.getArgumentTypes(desc2);
+
+ if (types2.length - types1.length != offset) {
+ return false;
+ }
+ for (int i = 0; i < types1.length; ++i) {
+ if (!types1[i].equals(types2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Visitor implementation.
+ */
+
+ @Override
+ public void visit(int version, int access,
+ String name, String signature,
+ String superName, String[] interfaceNames) {
+ className = name;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ captureLastMethodNode();
+ lastMethodNode = new MethodNode(access, name, desc, signature, exceptions);
+ return lastMethodNode;
+ }
+
+ @Override
+ public void visitEnd() {
+ captureLastMethodNode();
+ }
+
+ /**
+ * Creates a contract handle for the method last visited, if it was
+ * a contract method.
+ */
+ @Ensures("lastMethodNode == null")
+ protected void captureLastMethodNode() {
+ if (lastMethodNode == null) {
+ return;
+ }
+
+ ContractKind kind = ContractMethodSignatures.getKind(lastMethodNode);
+ if (kind != null) {
+ List<Long> lineNumbers =
+ ContractMethodSignatures.getLineNumbers(lastMethodNode);
+
+ if (kind.isClassContract() || kind.isHelperContract()) {
+ ClassContractHandle ch =
+ new ClassContractHandle(kind, className,
+ lastMethodNode, lineNumbers);
+ classHandles.add(ch);
+ } else {
+ MethodContractHandle mh =
+ new MethodContractHandle(kind, className,
+ lastMethodNode, lineNumbers);
+ internMethod(mh.getMethodName()).add(mh);
+ }
+ }
+
+ lastMethodNode = null;
+ }
+
+ /**
+ * Returns the contract handle collection corresponding to the
+ * method named {@code name}.
+ */
+ @Requires("name != null")
+ @Ensures({
+ "result != null",
+ "result == methodHandles.get(name)"
+ })
+ protected List<MethodContractHandle> internMethod(String name) {
+ ArrayList<MethodContractHandle> handles = methodHandles.get(name);
+ if (handles == null) {
+ handles = new ArrayList<MethodContractHandle>();
+ methodHandles.put(name, handles);
+ }
+ return handles;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java b/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java
new file mode 100644
index 0000000..e75bcc3
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.runtime.BlacklistManager;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.core.util.JavaUtils;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * A class transformer responsible for instrumenting classes with
+ * contracts. Only classes that have contracts will be instrumented.
+ *
+ * <p>The transformation process works in two steps:
+ *
+ * <ol>
+ * <li>The contract methods are extracted recursively from the
+ * contract class files and stored in the {@link ContractCodePool}.
+ * <li>Each individual class is instrumented with its contracts, taken
+ * from the pool.
+ * </ol>
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at AllowUnusedImport(ClassName.class)
+public class ContractClassFileTransformer implements ClassFileTransformer {
+ /**
+ * Find and store superclass information.
+ */
+ private class SuperInfoFinder extends ClassVisitor {
+ private SuperInfoFinder() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+
+ HashSet<String> assignable = new HashSet<String>();
+ assignable.add(name);
+ assignableToNames.put(name, assignable);
+
+ if (superName != null) {
+ superClassNames.put(name, superName);
+ assignable.add(superName);
+ findSuperInfo(superName);
+ assignable.addAll(assignableToNames.get(superName));
+ }
+
+ for (String ifaceName : interfaces) {
+ assignable.add(ifaceName);
+ findSuperInfo(ifaceName);
+ assignable.addAll(assignableToNames.get(ifaceName));
+ }
+ }
+
+ /**
+ * Look up information about super classes and assignable types
+ * for the class named {@code className}.
+ */
+ @Requires("ClassName.isBinaryName(className)")
+ private void findSuperInfo(String className) {
+ if (superClassNames.containsKey(className)) {
+ return;
+ }
+ if (blacklistManager.isIgnored(new ClassName(className)
+ .getQualifiedName())) {
+ findSuperInfoFromClass(className);
+ } else {
+ findSuperInfoFromClassFile(className);
+ }
+ }
+
+ @Requires("ClassName.isBinaryName(className)")
+ private void findSuperInfoFromClassFile(String className) {
+ try {
+ InputStream stream = JavaUtils.getClassInputStream(loader, className);
+ if (stream == null)
+ throw new NullPointerException();
+ ClassReader reader = new ClassReader(stream);
+ reader.accept(this, 0);
+ } catch (Exception e) {
+ addDefaultAssignable(className);
+ }
+ }
+
+ @Requires("ClassName.isBinaryName(className)")
+ private void findSuperInfoFromClass(String className) {
+ Class<?> clazz;
+ try {
+ String qName = new ClassName(className).getQualifiedName();
+ clazz = Class.forName(qName, false, loader);
+ } catch (ClassNotFoundException e) {
+ addDefaultAssignable(className);
+ return;
+ }
+
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass == null) {
+ addDefaultAssignable(className);
+ return;
+ }
+
+ HashSet<String> assignable = new HashSet<String>();
+ assignable.add(className);
+ assignableToNames.put(className, assignable);
+
+ String superName = superClass.getName().replace('.', '/');
+ superClassNames.put(className, superName);
+ assignable.add(superName);
+ findSuperInfo(superName);
+ assignable.addAll(assignableToNames.get(superName));
+
+ for (Class<?> iface : clazz.getInterfaces()) {
+ String ifaceName = iface.getName().replace('.', '/');
+ assignable.add(ifaceName);
+ findSuperInfo(ifaceName);
+ assignable.addAll(assignableToNames.get(ifaceName));
+ }
+ }
+
+ /**
+ * Add default super type information for the class named
+ * {@code className}. The default information makes the class
+ * a direct child of Object and assignable to it (and to itself)
+ * as well.
+ */
+ @Requires("className != null")
+ private void addDefaultAssignable(String className) {
+ if (!superClassNames.containsKey(className)) {
+ superClassNames.put(className, "java/lang/Object");
+ }
+ HashSet<String> assignable = new HashSet<String>();
+ assignable.add(className);
+ assignable.add("java/lang/Object");
+ assignableToNames.put(className, assignable);
+ }
+ }
+
+ /**
+ * A ClassWriter that does not load new classes. Tries to get the
+ * information from class files; an exception is made for
+ * blacklisted classes, which <em>are</em> loaded as usual. There
+ * should be no conflict as blacklisted hierarchies should be
+ * distinct from contracted ones.
+ */
+ protected class NonLoadingClassWriter extends ClassWriter {
+ @Requires("reader != null")
+ public NonLoadingClassWriter(ClassReader reader, int flags) {
+ super(reader, flags);
+ }
+
+ /*
+ * TODO(lenh): IMPORTANT NOTE. Computing stack frames in a purely
+ * forward fashion (from definitions to uses) using this method
+ * cannot be correct in some cases, no matter how accurate the
+ * type we return here. If two supertypes are possible (e.g., two
+ * interfaces) and one of them is needed for a following call,
+ * there's a 1/2 chance to pick the wrong one since the use is
+ * unknown at the time of the merge definition. It is unclear to
+ * me what the expected semantics of this method are, or what
+ * analysis (forward or backward) is actually performed by ASM
+ * when computing frames. Also, as is the case with the official
+ * implementation, this method does not handle interfaces
+ * completely.
+ */
+ @Override
+ protected String getCommonSuperClass(String className1, String className2) {
+ if (className1.equals(className2)) {
+ return className1;
+ }
+ SuperInfoFinder superInfoFinder = new SuperInfoFinder();
+ superInfoFinder.findSuperInfo(className1);
+ superInfoFinder.findSuperInfo(className2);
+ if (assignableToNames.get(className1).contains(className2)) {
+ return className2;
+ }
+ while (!assignableToNames.get(className2).contains(className1)) {
+ className1 = superClassNames.get(className1);
+ }
+ return className1;
+ }
+ }
+
+ protected BlacklistManager blacklistManager;
+
+ protected ClassLoader loader;
+
+ /*
+ * TODO(lenh): Use a LinkedHashMap for the two following fields,
+ * with some caching mechanism.
+ */
+
+ protected Map<String, Set<String>> assignableToNames =
+ new HashMap<String, Set<String>>();
+
+ protected Map<String, String> superClassNames = new HashMap<String, String>();
+
+ /**
+ * Constructs a new ContractClassFileTransformer.
+ */
+ public ContractClassFileTransformer() {
+ blacklistManager = BlacklistManager.getInstance();
+ }
+
+ /**
+ * Constructs a new ContractClassFileTransformer with default class
+ * loader {@code loader}. Subsequently,
+ * {@link #instrumentWithContracts(byte[],ContractAnalyzer)} may be
+ * called directly and will use the default loader.
+ */
+ public ContractClassFileTransformer(ClassLoader loader) {
+ this();
+ this.loader = loader;
+ }
+
+ /**
+ * Instruments the specified class, if necessary.
+ */
+ @Override
+ public byte[] transform(ClassLoader loader, String className,
+ Class<?> redefinedClass, ProtectionDomain protectionDomain,
+ byte[] bytecode) {
+ if (blacklistManager.isIgnored(className)) {
+ DebugUtils.info("agent", "ignoring " + className);
+ return null;
+ }
+ try {
+ this.loader = loader;
+ ContractAnalyzer contracts = analyze(className);
+ if (contracts == null) {
+ if (className.endsWith(JavaUtils.HELPER_CLASS_SUFFIX)) {
+ DebugUtils.info("agent", "adding source info to " + className);
+ return instrumentWithDebug(bytecode);
+ } else {
+ return null;
+ }
+ } else {
+ DebugUtils.info("agent", "adding contracts to " + className);
+ return instrumentWithContracts(bytecode, contracts);
+ }
+ } catch (Throwable e) {
+ DebugUtils.err("agent", "while instrumenting " + className, e);
+ /* Not reached. */
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Instruments the specified class with contracts.
+ */
+ @Requires({
+ "bytecode != null",
+ "contractBytecode != null"
+ })
+ @Ensures("result != null")
+ public byte[] transformWithContracts(byte[] bytecode, byte[] contractBytecode)
+ throws IllegalClassFormatException {
+ try {
+ ContractAnalyzer contracts =
+ extractContracts(new ClassReader(contractBytecode));
+ return instrumentWithContracts(bytecode, contracts);
+ } catch (Throwable t) {
+ /* If the class file contains errors, ASM will just crash. */
+ IllegalClassFormatException e = new IllegalClassFormatException();
+ e.initCause(t);
+ throw e;
+ }
+ }
+
+ /**
+ * Instruments the specified class with debug information.
+ */
+ @Requires("bytecode != null")
+ @Ensures("result != null")
+ public byte[] transformWithDebug(byte[] bytecode)
+ throws IllegalClassFormatException {
+ try {
+ return instrumentWithDebug(bytecode);
+ } catch (Throwable t) {
+ /* If the class file contains errors, ASM will just crash. */
+ IllegalClassFormatException e = new IllegalClassFormatException();
+ e.initCause(t);
+ throw e;
+ }
+ }
+
+ /**
+ * Extracts contract methods for the specified class, if necessary.
+ *
+ * @param className the class name
+ * @return the extracted contracts or {@code null} if the class has
+ * none and should not be instrumented
+ */
+ @Requires("ClassName.isBinaryName(className)")
+ protected ContractAnalyzer analyze(String className)
+ throws IOException {
+ /* Skip helper classes. */
+ if (className.endsWith(JavaUtils.HELPER_CLASS_SUFFIX)) {
+ return null;
+ }
+
+ /* Skip interfaces. */
+ String helperFileName = className + JavaUtils.HELPER_CLASS_SUFFIX
+ + Kind.CLASS.extension;
+ if (JavaUtils.resourceExists(loader, helperFileName)) {
+ return null;
+ }
+
+ /* Try to get contracts class file. */
+ InputStream contractStream =
+ JavaUtils.getContractClassInputStream(loader, className);
+ if (contractStream == null) {
+ return null;
+ }
+
+ return extractContracts(new ClassReader(contractStream));
+ }
+
+ /**
+ * Processes the specified reader and returns extracted contracts.
+ */
+ @Requires("reader != null")
+ protected ContractAnalyzer extractContracts(ClassReader reader) {
+ ContractAnalyzer contractAnalyzer = new ContractAnalyzer();
+ reader.accept(contractAnalyzer, ClassReader.EXPAND_FRAMES);
+ return contractAnalyzer;
+ }
+
+ /**
+ * Instruments the passed class file so that it contains contract
+ * methods and calls to these methods. The contract information is
+ * retrieved from the {@link ContractCodePool}.
+ *
+ * @param bytecode the bytecode of the class
+ * @param contracts the extracted contracts for the class
+ * @return the instrumented bytecode of the class
+ */
+ @Requires({
+ "bytecode != null",
+ "contracts != null"
+ })
+ @Ensures("result != null")
+ protected byte[] instrumentWithContracts(byte[] bytecode,
+ ContractAnalyzer contracts) {
+ ClassReader reader = new ClassReader(bytecode);
+ ClassWriter writer =
+ new NonLoadingClassWriter(reader,
+ ClassWriter.COMPUTE_FRAMES |
+ ClassWriter.COMPUTE_MAXS);
+
+ SpecificationClassAdapter adapter =
+ new SpecificationClassAdapter(writer, contracts);
+ reader.accept(adapter, ClassReader.EXPAND_FRAMES);
+
+ return writer.toByteArray();
+ }
+
+ /**
+ * Instruments the passed class file so that it contains debug
+ * information extraction from annotations.
+ */
+ @Requires("bytecode != null")
+ @Ensures("result != null")
+ private byte[] instrumentWithDebug(byte[] bytecode) {
+ ClassReader reader = new ClassReader(bytecode);
+ ClassWriter writer = new NonLoadingClassWriter(reader, 0);
+ reader.accept(new HelperClassAdapter(writer), ClassReader.EXPAND_FRAMES);
+ return writer.toByteArray();
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java b/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java
new file mode 100644
index 0000000..038c3f6
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.JavaUtils;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A class adapter that transforms injected contract methods to remove
+ * contract compilation artefacts:
+ *
+ * <ul>
+ * <li>fix calls to {@code access$n} synthetic methods for inner/nested
+ * classes.
+ * </ul>
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+class ContractFixingClassAdapter extends ClassVisitor {
+ /**
+ * A method adapter that amends calls to {@code access$n} synthetic
+ * methods. These methods are generated for access to members from
+ * inner/nested classes. Contract compilation may generate some of
+ * these, which are renamed and injected into the original class
+ * bytecode during instrumentation. Calls to these methods need to
+ * be fixed to use the new names.
+ */
+ protected static class AccessMethodAdapter extends MethodVisitor {
+ /**
+ * Constructs a new AccessMethodAdapter.
+ *
+ * @param mv the MethodVisitor this adapter delegates to
+ */
+ @Requires("mv != null")
+ public AccessMethodAdapter(MethodVisitor mv) {
+ super(Opcodes.ASM4, mv);
+ }
+
+ /**
+ * Converts calls to {@code access$n} synthetic methods to the
+ * equivalent injected methods.
+ */
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ if (!name.startsWith("access$")) {
+ mv.visitMethodInsn(opcode, owner, name, desc);
+ } else {
+ mv.visitMethodInsn(opcode, owner,
+ JavaUtils.SYNTHETIC_MEMBER_PREFIX + name, desc);
+ }
+ }
+ }
+
+ /**
+ * Constructs a new ContractFixingClassAdapter.
+ *
+ * @param cv the ClassVisitor this adapter delegates to
+ */
+ @Requires("cv != null")
+ public ContractFixingClassAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ /**
+ * Visits the specified method fixing method calls.
+ */
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = cv.visitMethod(access | Opcodes.ACC_SYNTHETIC,
+ name, desc, signature, exceptions);
+ return new AccessMethodAdapter(mv);
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ContractHandle.java b/src/com/google/java/contract/core/agent/ContractHandle.java
new file mode 100644
index 0000000..c8e348b
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractHandle.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.util.JavaUtils;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+
+/**
+ * A handle representing a contract method at run time. The handle
+ * gives access to the contract method as well as metadata required
+ * to instrument the elements it targets.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class })
+ at Invariant({
+ "getKind() != null",
+ "ClassName.isBinaryName(getClassName())",
+ "getKey() >= -1",
+ "getLineNumbers() == null " +
+ "|| ContractMethodSignatures.isLineNumberList(getLineNumbers())"
+})
+public class ContractHandle {
+ protected ContractKind kind;
+ protected String className;
+ protected int key;
+
+ protected MethodNode contractMethod;
+ protected List<Long> lineNumbers;
+
+ protected boolean injected;
+
+ /**
+ * Constructs a new ContractHandle.
+ *
+ * @param kind the kind of the contract handle
+ * @param className the name of the contracted class
+ * @param contractMethod the method node holding the actual
+ * implementation of the contract method
+ * @param lineNumbers the line numbers associated with the contract
+ */
+ @Requires({
+ "kind != null",
+ "ClassName.isBinaryName(className)",
+ "contractMethod != null",
+ "lineNumbers == null " +
+ "|| ContractMethodSignatures.isLineNumberList(lineNumbers)"
+ })
+ @Ensures({
+ "kind == getKind()",
+ "className.equals(getClassName())",
+ "contractMethod == getContractMethod()",
+ "lineNumbers == getLineNumbers()",
+ "!isInjected()"
+ })
+ protected ContractHandle(ContractKind kind, String className,
+ MethodNode contractMethod, List<Long> lineNumbers) {
+ this.kind = kind;
+ this.className = className;
+ key = ContractMethodSignatures.getId(contractMethod);
+
+ this.contractMethod = contractMethod;
+ if (!contractMethod.name.startsWith("com$google$java$contract$")) {
+ contractMethod.name =
+ JavaUtils.SYNTHETIC_MEMBER_PREFIX + contractMethod.name;
+ }
+ this.lineNumbers = lineNumbers;
+
+ this.injected = false;
+ }
+
+ public ContractKind getKind() {
+ return kind;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public int getKey() {
+ return key;
+ }
+
+ public MethodNode getContractMethod() {
+ return contractMethod;
+ }
+
+ public List<Long> getLineNumbers() {
+ return lineNumbers;
+ }
+
+ public boolean isInjected() {
+ return injected;
+ }
+
+ @Ensures("injected == isInjected()")
+ public void setInjected(boolean injected) {
+ this.injected = injected;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/ContractMethodSignature.java b/src/com/google/java/contract/core/agent/ContractMethodSignature.java
new file mode 100644
index 0000000..0832d99
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractMethodSignature.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.core.model.ContractKind;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Meta data attached to a contract method. The metadata is generated
+ * at compile-time by the contract compiler and provides information
+ * to the runtime Java agent about the purpose of the contract
+ * method. No two contract methods in a same class should have the
+ * same signature.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Target(ElementType.METHOD)
+ at Retention(RetentionPolicy.CLASS)
+public @interface ContractMethodSignature {
+ /**
+ * The kind of contract method, which determines its role with
+ * regard to the contracted class.
+ */
+ ContractKind kind();
+
+ /**
+ * The target of the contract method. Either the name of a method or
+ * the empty string if the contract targets the whole class.
+ */
+ String target() default "";
+
+ /**
+ * The ID of the contract method. IDs are used to distinguish
+ * between two contract methods with similar roles.
+ */
+ int id() default -1;
+
+ /**
+ * The line numbers where the original contract is located.
+ */
+ long[] lines() default {};
+}
diff --git a/src/com/google/java/contract/core/agent/ContractMethodSignatures.java b/src/com/google/java/contract/core/agent/ContractMethodSignatures.java
new file mode 100644
index 0000000..2378ac1
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/ContractMethodSignatures.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractKind;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Utility methods to read contract method signature components.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @see com.google.java.contract.core.agent.ContractMethodSignature
+ */
+class ContractMethodSignatures {
+ static final String CONTRACT_METHOD_SIGNATURE_DESC =
+ Type.getObjectType("com/google/java/contract/core/agent/ContractMethodSignature")
+ .getDescriptor();
+
+ @Requires("contractMethod != null")
+ static String getTarget(MethodNode contractMethod) {
+ return getMetaData(contractMethod, "target", String.class);
+ }
+
+ @Requires("contractMethod != null")
+ static ContractKind getKind(MethodNode contractMethod) {
+ String[] pair = getMetaData(contractMethod, "kind", String[].class);
+ if (pair != null) {
+ return Enum.valueOf(ContractKind.class, pair[1]);
+ } else if (contractMethod.name.startsWith("access$")) {
+ return ContractKind.ACCESS;
+ } else {
+ return null;
+ }
+ }
+
+ @Requires("contractMethod != null")
+ @Ensures("result >= -1")
+ static int getId(MethodNode contractMethod) {
+ Integer id = getMetaData(contractMethod, "id", Integer.class);
+ return id == null || id < 0 ? -1 : id;
+ }
+
+ @Requires("contractMethod != null")
+ @Ensures("result == null || isLineNumberList(result)")
+ static List<Long> getLineNumbers(MethodNode contractMethod) {
+ Object lines = getMetaData(contractMethod, "lines", Object.class);
+ return getLineNumbers(lines);
+ }
+
+ /**
+ * Converts either a {@code long[]} or {@code List<Long>} object as
+ * obtained by reading the annotation to a {@code List<Long>} with
+ * {@code null} instead of negative values to represent non-existent
+ * line information.
+ *
+ * TODO(lenh): ASM documentation states the returned annotation
+ * value should be a list but the library returns an array.
+ */
+ @SuppressWarnings("unchecked")
+ @Ensures({
+ "lines == null ? result == null : isLineNumberList(result)"
+ })
+ static List<Long> getLineNumbers(Object lines) {
+ if (lines == null) {
+ return null;
+ }
+
+ ArrayList<Long> lineNumbers = new ArrayList<Long>();
+ if (lines.getClass().isArray()) {
+ for (long line : (long[]) lines) {
+ lineNumbers.add(line < 1 ? null : line);
+ }
+ } else {
+ for (Long line : (List<Long>) lines) {
+ lineNumbers.add(line < 1 ? null : line);
+ }
+ }
+
+ return lineNumbers;
+ }
+
+ /**
+ * Returns {@code true} if {@code list} is a proper line number list
+ * as used by Contracts for Java in the bytecode instrumenter and
+ * associated components.
+ */
+ static boolean isLineNumberList(List<Long> list) {
+ if (list == null) {
+ return false;
+ }
+ for (Long line : list) {
+ if (line != null && line < 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Extracts the method signature field named {@code field}, with the
+ * type {@code clazz}, from the annotations of
+ * {@code contractMethod}.
+ *
+ * @param contractMethod the annotated method node
+ * @param field the name of the field to retrieve
+ * @param clazz the type of the value to return
+ * @return the value of the field, or {@code null}
+ */
+ @Requires({
+ "contractMethod != null",
+ "field != null",
+ "clazz != null"
+ })
+ @SuppressWarnings("unchecked")
+ static <T> T getMetaData(MethodNode contractMethod,
+ String field, Class<T> clazz) {
+ List<AnnotationNode> annotations = contractMethod.invisibleAnnotations;
+ if (annotations == null) {
+ return null;
+ }
+
+ for (AnnotationNode annotation : annotations) {
+ if (!annotation.desc.equals(CONTRACT_METHOD_SIGNATURE_DESC)) {
+ continue;
+ }
+
+ if (annotation.values == null) {
+ return null;
+ }
+
+ Iterator<?> it = annotation.values.iterator();
+ while (it.hasNext()) {
+ String name = (String) it.next();
+ Object value = it.next();
+ if (name.equals(field)) {
+ return (T) value;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/HelperClassAdapter.java b/src/com/google/java/contract/core/agent/HelperClassAdapter.java
new file mode 100644
index 0000000..e32a746
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/HelperClassAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.JavaUtils;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * Adds debug information to a helper class.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+class HelperClassAdapter extends ClassVisitor {
+ protected class HelperMethodAdapter extends LineNumberingMethodAdapter {
+ /**
+ * Constructs a new HelperMethodAdapter.
+ *
+ * @param mv the MethodVisitor this adapter delegates to
+ * @param access the access flags of the method
+ * @param name the name of the method
+ * @param desc the descriptor of the method
+ */
+ @Requires({
+ "mv != null",
+ "name != null",
+ "desc != null"
+ })
+ public HelperMethodAdapter(MethodVisitor mv, int access,
+ String name, String desc) {
+ super(mv, access, name, desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (Type.getType(desc).getInternalName().equals(
+ "com/google/java/contract/core/agent/ContractMethodSignature")) {
+ return new AnnotationVisitor(Opcodes.ASM4) {
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals("lines")) {
+ lineNumbers = ContractMethodSignatures.getLineNumbers(value);
+ }
+ }
+ };
+ }
+ return super.visitAnnotation(desc, visible);
+ }
+ }
+
+ @Requires("cv != null")
+ public HelperClassAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ /*
+ * Work around bogus file names and remove helper suffix if
+ * found. The compiler, as invoked in ContractJavaCompiler,
+ * produces SourceFile entries with a trailing ']' for no apparent
+ * reason; this method removes any trailing characters after the
+ * source extension.
+ */
+ String name =
+ source.substring(0, source.lastIndexOf(Kind.SOURCE.extension));
+ if (name.endsWith(JavaUtils.HELPER_CLASS_SUFFIX)) {
+ int lengthSansSuffix =
+ name.length() - JavaUtils.HELPER_CLASS_SUFFIX.length();
+ name = name.substring(0, lengthSansSuffix);
+ }
+ name += Kind.SOURCE.extension;
+ cv.visitSource(name, debug);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv =
+ cv.visitMethod(access, name, desc, signature, exceptions);
+ return new HelperMethodAdapter(mv, access, name, desc);
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java b/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java
new file mode 100644
index 0000000..60c81f0
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+/**
+ * A class adapter that adds a line number to methods.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("ContractMethodSignatures.isLineNumberList(lineNumbers)")
+class LineNumberingClassAdapter extends ClassVisitor {
+ protected List<Long> lineNumbers;
+
+ /**
+ * Constructs a new LineNumberingClassAdapter.
+ *
+ * @param cv the ClassVisitor this adapter delegates to
+ * @param lineNumbers the line numbers to associate with methods
+ */
+ @Requires({
+ "cv != null",
+ "ContractMethodSignatures.isLineNumberList(lineNumbers)"
+ })
+ public LineNumberingClassAdapter(ClassVisitor cv, List<Long> lineNumbers) {
+ super(Opcodes.ASM4, cv);
+ this.lineNumbers = lineNumbers;
+ }
+
+ /**
+ * Visits the specified method, adding line numbering.
+ */
+ @Override
+ public MethodVisitor visitMethod(int access, final String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = cv.visitMethod(access | Opcodes.ACC_SYNTHETIC,
+ name, desc, signature, exceptions);
+ return new LineNumberingMethodAdapter(mv, access | Opcodes.ACC_SYNTHETIC,
+ name, desc) {
+ @Override
+ protected void onMethodEnter() {
+ this.lineNumbers = LineNumberingClassAdapter.this.lineNumbers;
+ super.onMethodEnter();
+ }
+ };
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java b/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java
new file mode 100644
index 0000000..882ede7
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.JavaUtils;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.AdviceAdapter;
+
+import java.util.List;
+
+/**
+ * A method adapter that adds {@link #lineNumber} to the beginning of
+ * its instructions.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant(
+ "lineNumbers == null || " +
+ "ContractMethodSignatures.isLineNumberList(lineNumbers)"
+)
+abstract class LineNumberingMethodAdapter extends AdviceAdapter {
+ /**
+ * The line numbers to add to the method. This implementation always
+ * sets this field to {@code null}. Child classes must override this
+ * value before the call to {@link #onMethodEnter()} in order to get
+ * useful results.
+ */
+ protected List<Long> lineNumbers;
+
+ /**
+ * Constructs a new LineNumberingMethodAdapter.
+ *
+ * @param mv the MethodVisitor this adapter delegates to
+ * @param access the access flags of the method
+ * @param name the name of the method
+ * @param desc the descriptor of the method
+ */
+ @Requires({
+ "mv != null",
+ "name != null",
+ "desc != null"
+ })
+ public LineNumberingMethodAdapter(MethodVisitor mv, int access,
+ String name, String desc) {
+ super(Opcodes.ASM4, mv, access, name, desc);
+ lineNumbers = null;
+ }
+
+ @Override
+ protected void onMethodEnter() {
+ if (lineNumbers != null && !lineNumbers.isEmpty()) {
+ Long lineNumber = lineNumbers.get(0);
+ if (lineNumber != null) {
+ Label methodStart = new Label();
+ mark(methodStart);
+ mv.visitLineNumber(lineNumber.intValue(), methodStart);
+ }
+ }
+ }
+
+ @Override
+ protected void onMethodExit(int opcode) {
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ /* Ignore original line number information. */
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (lineNumbers != null) {
+ String prefix = JavaUtils.SUCCESS_VARIABLE_PREFIX + "$";
+ if (name.startsWith(prefix)) {
+ try {
+ int no = Integer.parseInt(name.substring(prefix.length()));
+ if (no < lineNumbers.size()) {
+ Long lineNumber = lineNumbers.get(no);
+ if (lineNumber != null) {
+ mv.visitLineNumber(lineNumber.intValue(), start);
+ }
+ }
+ } catch (NumberFormatException e) {
+ /* Not the variable we are looking for. */
+ }
+ }
+ }
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/MethodContractHandle.java b/src/com/google/java/contract/core/agent/MethodContractHandle.java
new file mode 100644
index 0000000..17737d4
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/MethodContractHandle.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractKind;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+
+/**
+ * A contract handle representing a contract method with method-wide
+ * effect.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("getMethodName() != null")
+public class MethodContractHandle extends ContractHandle {
+ protected String methodName;
+
+ /**
+ * Constructs a new MethodContractHandle.
+ *
+ * @param kind the kind of this MethodContractHandle
+ * @param className the class this handle belongs to
+ * @param contractMethod the {@link MethodNode} representing this
+ * handle's method
+ * @param lineNumbers the line numbers associated with the contract
+ */
+ @Requires({
+ "kind != null",
+ "className != null",
+ "contractMethod != null"
+ })
+ @Ensures({
+ "kind == getKind()",
+ "className.equals(getClassName())",
+ "contractMethod == getContractMethod()",
+ "lineNumbers == getLineNumbers()"
+ })
+ public MethodContractHandle(ContractKind kind, String className,
+ MethodNode contractMethod,
+ List<Long> lineNumbers) {
+ super(kind, className, contractMethod, lineNumbers);
+ methodName = ContractMethodSignatures.getTarget(contractMethod);
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/PreMain.java b/src/com/google/java/contract/core/agent/PreMain.java
new file mode 100644
index 0000000..0186aa7
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/PreMain.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.ContractEnvironment;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.core.util.JavaUtils;
+import org.objectweb.asm.ClassReader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.InvocationTargetException;
+import java.security.ProtectionDomain;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * A Java agent premain class that sets up class instrumentation for
+ * contracts or can be run as a standalone program that instruments
+ * class files.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public class PreMain {
+ @Invariant("transformer != null")
+ private static class DumpClassFileTransformer
+ implements ClassFileTransformer {
+ protected ClassFileTransformer transformer;
+
+ @Requires({
+ "parent != null",
+ "dumpDir != null"
+ })
+ public DumpClassFileTransformer(ClassFileTransformer parent,
+ String dumpDir) {
+ transformer = parent;
+ DebugUtils.setDumpDirectory(dumpDir);
+ }
+
+ @Override
+ public byte[] transform(ClassLoader loader, String className,
+ Class<?> redefinedClass,
+ ProtectionDomain protectionDomain,
+ byte[] bytecode)
+ throws IllegalClassFormatException {
+ byte[] data = transformer.transform(loader, className, redefinedClass,
+ protectionDomain, bytecode);
+ if (data != null) {
+ DebugUtils.dump(className, data, Kind.CLASS);
+ }
+ return data;
+ }
+ }
+
+ private static void configure() {
+ String configClass = System.getProperty("com.google.java.contract.configurator");
+ if (configClass != null) {
+ try {
+ Class<?> clazz = Class.forName(configClass);
+ Object instance = clazz.newInstance();
+ clazz.getMethod("configure", ContractEnvironment.class)
+ .invoke(instance, new AgentContractEnvironment());
+ } catch (ClassNotFoundException e) {
+ DebugUtils.warn("agent", "cannot find configurator class");
+ } catch (NoSuchMethodException e) {
+ DebugUtils.warn("agent", "cannot find configure method");
+ } catch (InvocationTargetException e) {
+ DebugUtils.warn("agent", "configure method threw an exception: "
+ + e.getTargetException().toString());
+ } catch (Exception e) {
+ DebugUtils.warn("agent",
+ "error during configure method: " + e.toString());
+ }
+ }
+ }
+
+ public static void premain(String args, Instrumentation inst) {
+ ClassFileTransformer transformer = new ContractClassFileTransformer();
+
+ String dumpDir = System.getProperty("com.google.java.contract.dump");
+ if (dumpDir != null) {
+ transformer = new DumpClassFileTransformer(transformer, dumpDir);
+ }
+
+ inst.addTransformer(transformer);
+
+ configure();
+ }
+
+ public static void main(String[] args)
+ throws IllegalClassFormatException, IOException {
+ String classout =
+ System.getProperty("com.google.java.contract.classoutput");
+ /* TODO(lenh): Separate class loader for source files. */
+ instrument(args, classout, null);
+ }
+
+ public static void instrument(String[] args, String classout,
+ ClassLoader loader)
+ throws IllegalClassFormatException, IOException {
+ ContractClassFileTransformer transformer;
+ if (loader == null) {
+ transformer = new ContractClassFileTransformer();
+ } else {
+ transformer = new ContractClassFileTransformer(loader);
+ }
+ configure();
+
+ for (String arg : args) {
+ String baseName = arg;
+ if (arg.endsWith(Kind.CLASS.extension)) {
+ baseName = baseName
+ .substring(0, baseName.length() - Kind.CLASS.extension.length());
+ }
+
+ /*
+ * Ignore helper class files, which are handled along with their
+ * interface.
+ */
+ if (baseName.endsWith(JavaUtils.HELPER_CLASS_SUFFIX)) {
+ continue;
+ }
+
+ /*
+ * Compute file names for all class files potentially
+ * involved.
+ */
+ File fileName = new File(baseName + Kind.CLASS.extension);
+ File contractFileName =
+ new File(baseName + JavaUtils.CONTRACTS_EXTENSION);
+ File helperFileName =
+ new File(baseName + JavaUtils.HELPER_CLASS_SUFFIX
+ + Kind.CLASS.extension);
+
+ byte[] bytecode = getBytes(fileName);
+ byte[] instrumented = null;
+ byte[] instrumentedHelper = null;
+
+ File outputFileName;
+ File helperOutputFileName;
+ if (classout == null) {
+ outputFileName = new File(baseName + JavaUtils.CONTRACTED_EXTENSION);
+ helperOutputFileName =
+ new File(baseName + JavaUtils.HELPER_CLASS_SUFFIX
+ + JavaUtils.CONTRACTED_EXTENSION);
+ } else {
+ String className = getClassName(bytecode);
+ String baseOutputName = classout + "/" + className;
+ outputFileName = new File(baseOutputName + Kind.CLASS.extension);
+ helperOutputFileName =
+ new File(baseOutputName + JavaUtils.HELPER_CLASS_SUFFIX
+ + Kind.CLASS.extension);
+ }
+
+ /*
+ * - If argument is an interface, instrument helper, copy interface.
+ * - If argument is a contracted class, instrument class.
+ * - Otherwise, copy class file.
+ */
+ if (helperFileName.isFile()) {
+ byte[] helperBytecode = getBytes(helperFileName);
+ instrumentedHelper = transformer.transformWithDebug(helperBytecode);
+ } else if (contractFileName.isFile()) {
+ byte[] contractBytecode = getBytes(contractFileName);
+ instrumented =
+ transformer.transformWithContracts(bytecode, contractBytecode);
+ }
+
+ outputFileName.getParentFile().mkdirs();
+ FileOutputStream out = new FileOutputStream(outputFileName);
+ out.write(instrumented == null ? bytecode : instrumented);
+ out.close();
+
+ if (instrumentedHelper != null) {
+ helperOutputFileName.getParentFile().mkdirs();
+ FileOutputStream helperOut = new FileOutputStream(helperOutputFileName);
+ helperOut.write(instrumentedHelper);
+ helperOut.close();
+ }
+ }
+ }
+
+ private static byte[] getBytes(File path) throws IOException {
+ FileInputStream in = new FileInputStream(path);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ in.close();
+
+ return out.toByteArray();
+ }
+
+ private static String getClassName(byte[] bytecode)
+ throws IllegalClassFormatException {
+ try {
+ return new ClassReader(bytecode).getClassName();
+ } catch (Throwable t) {
+ /* If the class file contains errors, ASM will just crash. */
+ IllegalClassFormatException e = new IllegalClassFormatException();
+ e.initCause(t);
+ throw e;
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java b/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java
new file mode 100644
index 0000000..6cd1a1e
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.util.DebugUtils;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.List;
+
+/**
+ * Bytecode class visitor. Initiates/delegates method instrumentation
+ * to instances of {@link SpecificationMethodAdapter}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at AllowUnusedImport(ClassName.class)
+ at Invariant({
+ "getClassName() == null || ClassName.isBinaryName(getClassName())",
+ "getContracts() != null",
+ "getParent() != null"
+})
+class SpecificationClassAdapter extends ClassVisitor {
+ protected String className;
+ protected ContractAnalyzer contracts;
+
+ public SpecificationClassAdapter(ClassVisitor cv,
+ ContractAnalyzer contracts) {
+ super(Opcodes.ASM4, cv);
+ this.contracts = contracts;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ className = name;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv =
+ cv.visitMethod(access, name, desc, signature, exceptions);
+ if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) {
+ return mv;
+ }
+
+ return new SpecificationMethodAdapter(this, mv, access, name, desc);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (contracts != null) {
+ List<ClassContractHandle> accesses =
+ contracts.getClassHandles(ContractKind.ACCESS);
+ for (ClassContractHandle h : accesses) {
+ h.getContractMethod().accept(cv);
+ }
+
+ List<ClassContractHandle> helpers =
+ contracts.getClassHandles(ContractKind.HELPER);
+ for (ClassContractHandle h : helpers) {
+ MethodNode methodNode = h.getContractMethod();
+ DebugUtils.info("instrument", "helper method "
+ + className + "." + methodNode.name
+ + methodNode.desc);
+ ClassVisitor visitor = cv;
+ List<Long> lineNumbers = h.getLineNumbers();
+ if (lineNumbers != null) {
+ visitor = new LineNumberingClassAdapter(visitor, lineNumbers);
+ }
+ methodNode.accept(new ContractFixingClassAdapter(visitor));
+ h.setInjected(true);
+ }
+ }
+ super.visitEnd();
+ }
+
+ /**
+ * Returns the name of the visited class.
+ */
+ String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns the class visitor this one delegates to.
+ */
+ ClassVisitor getParent() {
+ return cv;
+ }
+
+ /**
+ * Returns the contract analyzer of the visited class.
+ */
+ ContractAnalyzer getContracts() {
+ return contracts;
+ }
+}
diff --git a/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java b/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java
new file mode 100644
index 0000000..f546269
--- /dev/null
+++ b/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.agent;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.AdviceAdapter;
+import org.objectweb.asm.commons.Method;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A bytecode method visitor that instruments the original method to
+ * add calls to contract methods, and injects these contract methods,
+ * if necessary, into the enclosing class.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class })
+ at Invariant({
+ "methodStart != null",
+ "methodEnd != null",
+ "contracts != null",
+ "ClassName.isBinaryName(className)",
+ "methodName != null",
+ "methodDesc != null",
+ "Iterables.all(oldValueLocals, Predicates.between(0, null))",
+ "Iterables.all(signalOldValueLocals, Predicates.between(0, null))"
+})
+public class SpecificationMethodAdapter extends AdviceAdapter {
+ /*
+ * Reflection constants used in instrumentation code.
+ */
+ private static final Type CLASS_TYPE =
+ Type.getObjectType("java/lang/Class");
+ private static final Type EXCEPTION_TYPE =
+ Type.getObjectType("java/lang/Exception");
+ private static final Type CONTRACT_RUNTIME_TYPE =
+ Type.getObjectType("com/google/java/contract/core/runtime/ContractRuntime");
+ private static final Type CONTRACT_CONTEXT_TYPE =
+ Type.getObjectType("com/google/java/contract/core/runtime/ContractContext");
+ private static final Method GET_CLASS_METHOD =
+ Method.getMethod("java.lang.Class getClass()");
+ private static final Method GET_CONTEXT_METHOD =
+ Method.getMethod("com.google.java.contract.core.runtime.ContractContext "
+ + "getContext()");
+ private static final Method TRY_ENTER_CONTRACT_METHOD =
+ Method.getMethod("boolean tryEnterContract()");
+ private static final Method LEAVE_CONTRACT_METHOD =
+ Method.getMethod("void leaveContract()");
+ private static final Method TRY_ENTER_METHOD =
+ Method.getMethod("boolean tryEnter(Object)");
+ private static final Method LEAVE_METHOD =
+ Method.getMethod("void leave(Object)");
+
+ /*
+ * Used to bracket the entire original method to catch any exception
+ * that may arise and relay it to the exceptional postconditions.
+ */
+ protected Label methodStart;
+ protected Label methodEnd;
+
+ protected ContractAnalyzer contracts;
+ protected String className;
+ protected String methodName;
+ protected String methodDesc;
+ protected Type thisType;
+
+ protected boolean statik;
+ protected boolean isConstructor;
+ protected boolean isStaticInit;
+
+ protected int contextLocal;
+ protected int checkInvariantsLocal;
+ protected List<Integer> oldValueLocals;
+ protected List<Integer> signalOldValueLocals;
+
+ protected SpecificationClassAdapter classAdapter;
+
+ protected boolean withPreconditions;
+ protected boolean withPostconditions;
+ protected boolean withInvariants;
+
+ /**
+ * Constructs a new SpecificationClassAdapter.
+ *
+ * @param ca the class adapter which has spawned this method adapter
+ * @param mv the method visitor to delegate to
+ * @param access the method access bit mask
+ * @param methodName the name of the method
+ * @param methodDesc the descriptor of the method
+ */
+ @Requires({
+ "ca != null",
+ "mv != null",
+ "methodName != null",
+ "methodDesc != null"
+ })
+ public SpecificationMethodAdapter(SpecificationClassAdapter ca,
+ MethodVisitor mv,
+ int access, String methodName,
+ String methodDesc) {
+ super(Opcodes.ASM4, mv, access, methodName, methodDesc);
+
+ methodStart = new Label();
+ methodEnd = new Label();
+
+ this.contracts = ca.getContracts();
+ className = ca.getClassName();
+ this.methodName = methodName;
+ this.methodDesc = methodDesc;
+ thisType = Type.getType("L" + className + ";");
+
+ statik = (access & ACC_STATIC) != 0;
+ isConstructor = methodName.equals("<init>");
+ isStaticInit = methodName.endsWith("<clinit>");
+
+ contextLocal = -1;
+ checkInvariantsLocal = -1;
+ oldValueLocals = new ArrayList<Integer>();
+ signalOldValueLocals = new ArrayList<Integer>();
+
+ classAdapter = ca;
+
+ ActivationRuleManager am = ActivationRuleManager.getInstance();
+ withPreconditions = am.hasPreconditionsEnabled(className);
+ withPostconditions = am.hasPostconditionsEnabled(className);
+ withInvariants = am.hasInvariantsEnabled(className);
+ }
+
+ /**
+ * Returns {@code true} if the specified label is resolved, that is,
+ * is associated with an offset in the bytecode of the method.
+ */
+ @Requires("label != null")
+ protected static boolean labelIsResolved(Label label) {
+ try {
+ label.getOffset();
+ } catch (IllegalStateException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ /*
+ * Handle bogus debug information where labels are not on
+ * instruction boundaries. EMMA, the coverage instrumenter in use
+ * at Google, produces such code.
+ */
+ if (!labelIsResolved(start)) {
+ return;
+ }
+ if (!labelIsResolved(end)) {
+ end = start;
+ }
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ /**
+ * Advises the method by injecting invariants, precondition
+ * assertions, and old value computations, before the original code.
+ */
+ @Override
+ protected void onMethodEnter() {
+ if (withPreconditions || withPostconditions || withInvariants) {
+ enterContractedMethod();
+
+ if (withPostconditions) {
+ allocateOldValues(ContractKind.OLD, oldValueLocals);
+ allocateOldValues(ContractKind.SIGNAL_OLD, signalOldValueLocals);
+ }
+
+ mark(methodStart);
+
+ Label skip = enterBusySection();
+
+ if (withInvariants && !statik && !isConstructor && !isStaticInit) {
+ invokeInvariants();
+ }
+
+ if (withPreconditions) {
+ invokePreconditions();
+ }
+
+ if (withPostconditions) {
+ invokeOldValues(ContractKind.OLD, oldValueLocals);
+ invokeOldValues(ContractKind.SIGNAL_OLD, signalOldValueLocals);
+ }
+
+ leaveBusySection(skip);
+ }
+ }
+
+ /**
+ * Advises the method by injecting postconditions and invariants
+ * on exit from the original code ({@code return} or {@throw}).
+ */
+ @Override
+ protected void onMethodExit(int opcode) {
+ if ((withPreconditions || withPostconditions || withInvariants)
+ && opcode != ATHROW) {
+ if (withPostconditions || withInvariants) {
+ Label skip = enterBusySection();
+
+ if (withPostconditions) {
+ Type returnType = Type.getReturnType(methodDesc);
+ int returnIndex = -1;
+ if (returnType.getSort() != Type.VOID) {
+ if (returnType.getSize() == 2) {
+ dup2();
+ } else {
+ dup();
+ }
+ returnIndex = newLocal(returnType);
+ storeLocal(returnIndex);
+ }
+ invokeCommonPostconditions(ContractKind.POST, oldValueLocals,
+ returnIndex);
+ }
+
+ if (withInvariants && !statik) {
+ invokeInvariants();
+ }
+
+ leaveBusySection(skip);
+ }
+ leaveContractedMethod();
+ }
+ }
+
+ /**
+ * Advises the method by injecting exceptional postconditions and
+ * invariants after the original code. This code only gets executed
+ * if an exception has been thrown (otherwise, a {@code return}
+ * instruction would have ended execution of the method already).
+ */
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (withPreconditions || withPostconditions || withInvariants) {
+ mark(methodEnd);
+ catchException(methodStart, methodEnd, null);
+
+ if (withPostconditions) {
+ Label skipEx = new Label();
+ dup();
+ instanceOf(EXCEPTION_TYPE);
+ ifZCmp(EQ, skipEx);
+
+ Label skip = enterBusySection();
+ int throwIndex = newLocal(EXCEPTION_TYPE);
+ checkCast(EXCEPTION_TYPE);
+ storeLocal(throwIndex);
+
+ invokeCommonPostconditions(ContractKind.SIGNAL, signalOldValueLocals,
+ throwIndex);
+ if (withInvariants && !statik) {
+ invokeInvariants();
+ }
+
+ loadLocal(throwIndex);
+ leaveBusySection(skip);
+
+ mark(skipEx);
+ }
+
+ /*
+ * The exception to throw is on the stack and
+ * leaveContractedMethod() does not alter that fact.
+ */
+ leaveContractedMethod();
+ throwException();
+ }
+ super.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * Injects code to allocate the local variables needed to hold old
+ * values for the postconditions of the method. These variables are
+ * initialized to {@code null}.
+ *
+ * @param kind either OLD or SIGNAL_OLD
+ * @param list the list that will hold the allocated indexes
+ */
+ @Requires({
+ "kind != null",
+ "kind.isOld()",
+ "list != null"
+ })
+ protected void allocateOldValues(ContractKind kind, List<Integer> list) {
+ List<MethodContractHandle> olds =
+ contracts.getMethodHandles(kind, methodName, methodDesc, 0);
+ if (olds.isEmpty()) {
+ return;
+ }
+
+ Integer[] locals = new Integer[olds.size()];
+ for (MethodContractHandle h : olds) {
+ int k = h.getKey();
+ locals[k] = newLocal(Type.getReturnType(h.getContractMethod().desc));
+ push((String) null);
+ storeLocal(locals[k]);
+ }
+ list.addAll(Arrays.asList(locals));
+ }
+
+ /**
+ * Injects calls to old value contract methods. old value contract
+ * methods get called with, in this order:
+ *
+ * <ul>
+ * <li>the {@code this} pointer, if any;
+ * <li>and the original method's parameters.
+ * </ul>
+ *
+ * @param kind either OLD or SIGNAL_OLD
+ * @param list the list that holds the allocated old value variable
+ * indexes
+ */
+ @Requires({
+ "kind != null",
+ "kind.isOld()",
+ "list != null"
+ })
+ protected void invokeOldValues(ContractKind kind, List<Integer> list) {
+ List<MethodContractHandle> olds =
+ contracts.getMethodHandles(kind, methodName, methodDesc, 0);
+ if (olds.isEmpty()) {
+ return;
+ }
+
+ for (MethodContractHandle h : olds) {
+ MethodNode contractMethod = injectContractMethod(h);
+ int k = h.getKey();
+
+ if (!statik) {
+ loadThis();
+ }
+ loadArgs();
+ invokeContractMethod(contractMethod);
+
+ storeLocal(list.get(k));
+ }
+ }
+
+ /**
+ * Injects calls to invariant contract methods. Invariant contract
+ * methods get called with the {@code this} pointer, if any.
+ */
+ protected void invokeInvariants() {
+ ClassContractHandle h = contracts.getClassHandle(ContractKind.INVARIANT);
+ if (h == null) {
+ return;
+ }
+
+ MethodNode contractMethod = injectContractMethod(h);
+
+ Label skipInvariants = new Label();
+ if (isConstructor) {
+ loadThis();
+ invokeVirtual(thisType, GET_CLASS_METHOD);
+ loadThisClass();
+ ifCmp(CLASS_TYPE, NE, skipInvariants);
+ } else {
+ loadLocal(checkInvariantsLocal);
+ ifZCmp(EQ, skipInvariants);
+ }
+
+ if (!statik) {
+ loadThis();
+ }
+ invokeContractMethod(contractMethod);
+
+ mark(skipInvariants);
+ }
+
+ /**
+ * Injects calls to precondition contract methods. Precondition
+ * contract methods get called with, in this order:
+ *
+ * <ul>
+ * <li>the {@code this} pointer, if any;
+ * <li>and the original method's parameters.
+ * </ul>
+ */
+ protected void invokePreconditions() {
+ MethodContractHandle h =
+ contracts.getMethodHandle(ContractKind.PRE, methodName, methodDesc, 0);
+ if (h == null) {
+ return;
+ }
+
+ MethodNode contractMethod = injectContractMethod(h);
+ if (!statik) {
+ loadThis();
+ }
+ loadArgs();
+ invokeContractMethod(contractMethod);
+ }
+
+ /**
+ * Injects calls to postcondition (or exceptional postcondition)
+ * contract methods. Postcondition contract methods get called with,
+ * in this order:
+ *
+ * <ul>
+ * <li>the {@code this} pointer, if any;
+ * <li>the original method's parameters;
+ * <li>the return value, if the original method is not void, or the
+ * exception object;
+ * <li>and old values if any.
+ * </ul>
+ *
+ * @param kind the kind of postcondition contract to invoke
+ * @param oldLocals a list of old value variables
+ * @param extraIndex the index of the local variable that holds the
+ * return value, or exception object, of the method, or -1 if none
+ */
+ @Requires({
+ "kind != null",
+ "kind.isPostcondition()",
+ "oldLocals != null",
+ "extraIndex >= -1"
+ })
+ protected void invokeCommonPostconditions(ContractKind kind,
+ List<Integer> oldLocals, int extraIndex) {
+ MethodContractHandle h =
+ contracts.getMethodHandle(kind, methodName, methodDesc,
+ getPostDescOffset(oldLocals, extraIndex));
+ if (h == null) {
+ return;
+ }
+
+ MethodNode contractMethod = injectContractMethod(h);
+
+ if (!statik) {
+ loadThis();
+ }
+ loadArgs();
+
+ if (extraIndex != -1) {
+ loadLocal(extraIndex);
+ }
+
+ for (Integer oldIndex : oldLocals) {
+ loadLocal(oldIndex);
+ }
+
+ invokeContractMethod(contractMethod);
+ }
+
+ /**
+ * Computes the method descriptor offset of a postcondition (or an
+ * exceptional postcondition). The method descriptor offset is the
+ * number of extra arguments the contract method has in addition to
+ * those inherited from the original method.
+ *
+ * @param oldLocals the list of old value variables
+ * @param extraIndex the local variable index of an extra parameter,
+ * or -1 if none
+ */
+ @Requires({
+ "oldLocals != null",
+ "extraIndex >= -1"
+ })
+ @Ensures("result >= 0")
+ protected int getPostDescOffset(List<Integer> oldLocals, int extraIndex) {
+ int off = 0;
+ if (extraIndex != -1) {
+ ++off;
+ }
+ off += oldLocals.size();
+ return off;
+ }
+
+ /**
+ * Injects the specified contract method code into the current
+ * class, and returns a new Method object representing the injected
+ * method. This is done by bypassing the SpecificationClassAdapter
+ * and talking directly to its parent (usually, a class writer).
+ *
+ * @param handle the handle from the code pool that holds code and
+ * meta-information about the contract method
+ * @return the method node to invoke
+ */
+ @Requires("handle != null")
+ @Ensures("result != null")
+ protected MethodNode injectContractMethod(ContractHandle handle) {
+ MethodNode methodNode = handle.getContractMethod();
+
+ if (!handle.isInjected()) {
+ DebugUtils.info("instrument", "contract method "
+ + className + "." + methodNode.name
+ + methodNode.desc);
+ ClassVisitor cv = classAdapter.getParent();
+ List<Long> lineNumbers = handle.getLineNumbers();
+ if (lineNumbers != null) {
+ cv = new LineNumberingClassAdapter(cv, lineNumbers);
+ }
+ methodNode.accept(new ContractFixingClassAdapter(cv));
+ handle.setInjected(true);
+ }
+
+ return methodNode;
+ }
+
+ @Requires("contractMethod != null")
+ protected void invokeContractMethod(MethodNode contractMethod) {
+ if (!statik) {
+ mv.visitMethodInsn(INVOKESPECIAL, className,
+ contractMethod.name, contractMethod.desc);
+ } else {
+ mv.visitMethodInsn(INVOKESTATIC, className,
+ contractMethod.name, contractMethod.desc);
+ }
+ }
+
+ /**
+ * Marks the beginning of a busy section. A busy section is skipped
+ * if the context is already busy.
+ */
+ @Requires({
+ "contextLocal >= 0",
+ "checkInvariantsLocal >= 0"
+ })
+ @Ensures("result != null")
+ protected Label enterBusySection() {
+ Label skip = new Label();
+ loadLocal(contextLocal);
+ invokeVirtual(CONTRACT_CONTEXT_TYPE, TRY_ENTER_CONTRACT_METHOD);
+ ifZCmp(EQ, skip);
+ return skip;
+ }
+
+ /**
+ * Marks the end of a busy section.
+ */
+ @Requires({
+ "contextLocal >= 0",
+ "skip != null"
+ })
+ protected void leaveBusySection(Label skip) {
+ loadLocal(contextLocal);
+ invokeVirtual(CONTRACT_CONTEXT_TYPE, LEAVE_CONTRACT_METHOD);
+ mark(skip);
+ }
+
+ /**
+ * Loads the static class object this method belongs to on the
+ * stack.
+ */
+ protected void loadThisClass() {
+ visitLdcInsn(thisType);
+ }
+
+ /**
+ * Retrieves busy state of the current object.
+ */
+ @Ensures({
+ "contextLocal >= 0",
+ "checkInvariantsLocal >= 0"
+ })
+ protected void enterContractedMethod() {
+ contextLocal = newLocal(CONTRACT_CONTEXT_TYPE);
+ checkInvariantsLocal = newLocal(Type.BOOLEAN_TYPE);
+ invokeStatic(CONTRACT_RUNTIME_TYPE, GET_CONTEXT_METHOD);
+ dup();
+ storeLocal(contextLocal);
+ if (statik) {
+ loadThisClass();
+ } else {
+ loadThis();
+ }
+ invokeVirtual(CONTRACT_CONTEXT_TYPE, TRY_ENTER_METHOD);
+ storeLocal(checkInvariantsLocal);
+ }
+
+ /**
+ * Cancels busy state of the current object.
+ */
+ @Requires("contextLocal >= 0")
+ protected void leaveContractedMethod() {
+ Label skip = new Label();
+ loadLocal(checkInvariantsLocal);
+ ifZCmp(EQ, skip);
+
+ loadLocal(contextLocal);
+ if (statik) {
+ loadThisClass();
+ } else {
+ loadThis();
+ }
+ invokeVirtual(CONTRACT_CONTEXT_TYPE, LEAVE_METHOD);
+
+ mark(skip);
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java b/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java
new file mode 100644
index 0000000..2049d44
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModel;
+import com.google.java.contract.core.model.TypeName;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.ElementScanner6;
+
+/**
+ * Abstract base class providing annotation processing facilities
+ * used to build types.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at Invariant("utils != null")
+abstract class AbstractTypeBuilder
+ extends ElementScanner6<Void, ElementModel> {
+ protected FactoryUtils utils;
+
+ protected DiagnosticManager diagnosticManager;
+
+ @Requires("utils != null")
+ protected AbstractTypeBuilder(FactoryUtils utils,
+ DiagnosticManager diagnosticManager) {
+ this.utils = utils;
+ this.diagnosticManager = diagnosticManager;
+ }
+
+ /**
+ * A global iterator to use to get contract annotation line number
+ * information.
+ */
+ protected Iterator<Long> rootLineNumberIterator;
+
+ /**
+ * Creates a blank {@code ContractAnnotationModel} from
+ * an {@code AnnotationMirror}. The returned model is created with
+ * the correct properties for the provided context but does not
+ * contain any of the clauses from {@code annotation}.
+ *
+ * @param parent the target of the annotation
+ * @param annotation the annotation
+ * @param primary whether this is a primary contract annotation
+ * @param owner the owner of this annotation
+ * @return the contract model of this annotation
+ */
+ @Requires({
+ "parent != null",
+ "annotation != null",
+ "owner != null",
+ "utils.isContractAnnotation(annotation)"
+ })
+ @Ensures("result != null")
+ private ContractAnnotationModel createBlankContractModel(Element parent,
+ AnnotationMirror annotation, boolean primary, ClassName owner) {
+ ElementKind kind = utils.getAnnotationKindForName(annotation);
+
+ boolean virtual;
+ TypeName returnType;
+ switch (parent.getKind()) {
+ default:
+ virtual =
+ parent.getKind()
+ != javax.lang.model.element.ElementKind.INTERFACE;
+ returnType = null;
+ break;
+ case CONSTRUCTOR:
+ case METHOD:
+ virtual =
+ parent.getEnclosingElement().getKind()
+ != javax.lang.model.element.ElementKind.INTERFACE;
+ ExecutableElement method = (ExecutableElement) parent;
+ switch (method.getReturnType().getKind()) {
+ case VOID:
+ /* For void methods. */
+ returnType = utils.getTypeNameForType(method.getReturnType());
+ break;
+ case NONE:
+ /* For constructors. */
+ returnType = null;
+ break;
+ case PACKAGE:
+ /* Should not happen. */
+ throw new RuntimeException(
+ "ExecutableElement has PACKAGE return type");
+ default:
+ returnType = utils.getTypeNameForType(
+ utils.typeUtils.erasure(method.getReturnType()));
+ }
+ }
+
+ return new ContractAnnotationModel(kind, primary, virtual,
+ owner, returnType);
+ }
+
+ /**
+ * Creates a {@code ContractAnnotationModel} from
+ * an {@code AnnotationMirror}.
+ *
+ * @param parent the target of the annotation
+ * @param annotation the annotation
+ * @param primary whether this is a primary contract annotation
+ * @param owner the owner of this annotation
+ * @return the contract model of this annotation, or {@code null} if
+ * the annotation contains no contract (no or empty value)
+ */
+ @Requires({
+ "parent != null",
+ "annotation != null",
+ "owner != null",
+ "utils.isContractAnnotation(annotation)"
+ })
+ protected ContractAnnotationModel createContractModel(Element parent,
+ AnnotationMirror annotation, boolean primary, ClassName owner) {
+ ContractAnnotationModel model = createBlankContractModel(
+ parent, annotation, primary, owner);
+ List<Long> lineNumbers = null;
+ if (rootLineNumberIterator == null) {
+ lineNumbers = getLineNumbers(parent, annotation);
+ }
+
+ AnnotationValue lastAnnotationValue = null;
+ for (AnnotationValue annotationValue :
+ annotation.getElementValues().values()) {
+ @SuppressWarnings("unchecked")
+ List<? extends AnnotationValue> values =
+ (List<? extends AnnotationValue>) annotationValue.getValue();
+
+ Iterator<? extends AnnotationValue> iterValue = values.iterator();
+ Iterator<Long> iterLineNumber;
+ if (rootLineNumberIterator != null) {
+ iterLineNumber = rootLineNumberIterator;
+ } else {
+ iterLineNumber = lineNumbers.iterator();
+ }
+ while (iterValue.hasNext()) {
+ String value = (String) iterValue.next().getValue();
+ Long lineNumber =
+ iterLineNumber.hasNext() ? iterLineNumber.next() : null;
+ model.addValue(value, lineNumber);
+ }
+ lastAnnotationValue = annotationValue;
+ }
+ if (model.getValues().isEmpty()) {
+ diagnosticManager.warning("No contracts specified in annotation.",
+ null, 0, 0, 0,
+ parent, annotation, lastAnnotationValue);
+ return null;
+ }
+ AnnotationSourceInfo sourceInfo =
+ new AnnotationSourceInfo(parent, annotation, lastAnnotationValue,
+ model.getValues());
+ model.setSourceInfo(sourceInfo);
+ return model;
+ }
+
+ /**
+ * Visits an annotation and adds a corresponding node to the
+ * specified Element.
+ *
+ * Despite the name, this method is not inherited through any
+ * visitor interface. It is not intended for external calls.
+ *
+ * @param parent the target of the annotation
+ * @param annotation the annotation
+ * @param primary whether this is a primary contract annotation
+ * @param owner the owner of this annotation
+ * @param p the element to add the created annotation to
+ *
+ * @see ContractAnnotationModel
+ */
+ @Requires({
+ "parent != null",
+ "annotation != null",
+ "owner != null",
+ "p != null"
+ })
+ protected void visitAnnotation(
+ Element parent, AnnotationMirror annotation,
+ boolean primary, ClassName owner, ElementModel p) {
+ if (utils.isContractAnnotation(annotation)) {
+ ContractAnnotationModel model =
+ createContractModel(parent, annotation, primary, owner);
+ if (model != null) {
+ p.addEnclosedElement(model);
+ }
+ }
+ }
+
+ /**
+ * Returns the line numbers associated with {@code annotation} if
+ * available.
+ */
+ @Requires({
+ "parent != null",
+ "annotation != null"
+ })
+ @Ensures("result != null")
+ @SuppressWarnings("unchecked")
+ protected List<Long> getLineNumbers(Element parent,
+ AnnotationMirror annotation) {
+ if (JavaUtils.classExists("com.sun.source.util.Trees")) {
+ try {
+ return (List<Long>) Class
+ .forName("com.google.java.contract.core.apt.JavacUtils")
+ .getMethod("getLineNumbers", ProcessingEnvironment.class,
+ Element.class, AnnotationMirror.class)
+ .invoke(null, utils.processingEnv, parent, annotation);
+ } catch (Exception e) {
+ return Collections.emptyList();
+ }
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Returns the import statements in effect in the compilation unit
+ * containing {@code element}.
+ */
+ @Requires("element != null")
+ @Ensures("result != null")
+ @SuppressWarnings("unchecked")
+ protected Set<String> getImportNames(Element element) {
+ if (JavaUtils.classExists("com.sun.source.util.Trees")) {
+ try {
+ return (Set<String>) Class
+ .forName("com.google.java.contract.core.apt.JavacUtils")
+ .getMethod("getImportNames", ProcessingEnvironment.class,
+ Element.class)
+ .invoke(null, utils.processingEnv, element);
+ } catch (Exception e) {
+ return Collections.emptySet();
+ }
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ /**
+ * Scans a list of annotations and call
+ * {@link #visitAnnotation(Element,AnnotationMirror,boolean,ClassName,ElementModel)}
+ * on each one of them, in order.
+ *
+ * @see ContractAnnotationModel
+ */
+ @Requires({
+ "parent != null",
+ "owner != null",
+ "p != null"
+ })
+ protected void scanAnnotations(Element parent,
+ boolean primary, ClassName owner, ElementModel p) {
+ for (AnnotationMirror ann : parent.getAnnotationMirrors()) {
+ visitAnnotation(parent, ann, primary, owner, p);
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/AnnotationProcessor.java b/src/com/google/java/contract/core/apt/AnnotationProcessor.java
new file mode 100644
index 0000000..732b7ba
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/AnnotationProcessor.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.sun.tools.javac.main.OptionName;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.util.Options;
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.core.util.ElementScanner;
+import com.google.java.contract.core.util.SyntheticJavaFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementScanner6;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * A JSR 269 annotation processor that builds contract Java source
+ * files from annotated classes, and compiles them.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at AllowUnusedImport(ElementKind.class)
+ at SupportedAnnotationTypes("*")
+ at SupportedOptions({
+ AnnotationProcessor.OPT_DEBUG,
+ AnnotationProcessor.OPT_DUMP,
+ AnnotationProcessor.OPT_SOURCEPATH,
+ AnnotationProcessor.OPT_CLASSPATH,
+ AnnotationProcessor.OPT_CLASSOUTPUT,
+ AnnotationProcessor.OPT_DEPSPATH,
+ AnnotationProcessor.OPT_EXPERIMENTAL
+})
+public class AnnotationProcessor extends AbstractProcessor {
+ /**
+ * This option adds instructions for a debug trace to contract
+ * code. For the trace to actually appear at run time, the
+ * {@code com.google.java.contract.log.contract} property must be {@code true}.
+ */
+ protected static final String OPT_DEBUG = "com.google.java.contract.debug";
+
+ /**
+ * This option dumps the generated Java source files in the
+ * specified directory (defaults to {@code contracts_for_java.out}).
+ */
+ protected static final String OPT_DUMP = "com.google.java.contract.dump";
+
+ /**
+ * This option sets the source path for the compilation of the
+ * generated source files. It should be the same as the class path
+ * for the sources themselves.
+ */
+ protected static final String OPT_SOURCEPATH = "com.google.java.contract.sourcepath";
+
+ /**
+ * This option sets the class path for the compilation of the
+ * generated source files. It should be the same as the class path
+ * for the sources themselves.
+ */
+ protected static final String OPT_CLASSPATH = "com.google.java.contract.classpath";
+
+ /**
+ * This option sets the class output path for the compilation of the
+ * generated source files.
+ */
+ protected static final String OPT_CLASSOUTPUT = "com.google.java.contract.classoutput";
+
+ /**
+ * This option sets the path for source dependency auxiliary files.
+ *
+ * @see SourcePreprocessor
+ */
+ protected static final String OPT_DEPSPATH = "com.google.java.contract.depspath";
+
+ /**
+ * This option enables experimental Contracts for Java features. <em>These
+ * features may or may not be included in future releases, or
+ * change, without warning.</em>
+ */
+ protected static final String OPT_EXPERIMENTAL = "com.google.java.contract.experimental";
+
+ protected TypeFactory factory;
+ protected FactoryUtils utils;
+
+ protected String sourcePath;
+ protected String classPath;
+ protected String outputDirectory;
+
+ protected boolean debug;
+ protected boolean dump;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+
+ Map<String, String> options = processingEnv.getOptions();
+ debug = options.containsKey(OPT_DEBUG);
+ dump = options.containsKey(OPT_DUMP);
+ String dumpDir = options.get(OPT_DUMP);
+ if (dumpDir != null) {
+ DebugUtils.setDumpDirectory(dumpDir);
+ }
+
+ utils = new FactoryUtils(processingEnv);
+ factory = new TypeFactory(utils, options.get(OPT_DEPSPATH));
+
+ setupPaths();
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations,
+ RoundEnvironment roundEnv) {
+ Set<TypeElement> rootElements = getContractedRootElements(roundEnv);
+ if (rootElements.isEmpty()) {
+ return false;
+ }
+
+ DiagnosticManager diagnosticManager = new DiagnosticManager();
+
+ List<TypeModel> types = createTypes(rootElements, diagnosticManager);
+ boolean success = diagnosticManager.getErrorCount() == 0;
+
+ ArrayList<SyntheticJavaFile> sources =
+ new ArrayList<SyntheticJavaFile>(types.size());
+ if (success) {
+ for (TypeModel type : types) {
+ ContractWriter writer = new ContractWriter(debug);
+ type.accept(writer);
+ sources.add(new SyntheticJavaFile(type.getName().getBinaryName(),
+ writer.toByteArray(),
+ writer.getLineNumberMap()));
+ }
+
+ if (dump) {
+ dumpSources(types, sources);
+ }
+
+ try {
+ ContractJavaCompiler compiler =
+ new ContractJavaCompiler(sourcePath, classPath, outputDirectory);
+ CompilationTask task = compiler.getTask(sources, diagnosticManager);
+ success = task.call();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (!success || diagnosticManager.getCount() != 0) {
+ for (DiagnosticManager.Report r : diagnosticManager) {
+ printDiagnostic(r);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets class and output paths from command-line options.
+ */
+ private void setupPaths() {
+ sourcePath = processingEnv.getOptions().get(OPT_SOURCEPATH);
+ classPath = processingEnv.getOptions().get(OPT_CLASSPATH);
+ outputDirectory = processingEnv.getOptions().get(OPT_CLASSOUTPUT);
+
+ /*
+ * Not using instanceof here because than in every case the JVM
+ * tries to load the JavacProcessingEnvironment-class file which,
+ * for instance, is not possible with an IBM JVM.
+ *
+ * TODO(lenh): This may not work; use reflection call instead.
+ */
+ if (processingEnv.getClass().getName().equals(
+ "com.sun.tools.javac.processing.JavacProcessingEnvironment")) {
+ JavacProcessingEnvironment javacEnv =
+ (JavacProcessingEnvironment) processingEnv;
+ Options options = Options.instance(javacEnv.getContext());
+
+ if (sourcePath == null) {
+ sourcePath = options.get(OptionName.SOURCEPATH);
+ }
+
+ if (classPath == null) {
+ String classPath1 = options.get(OptionName.CP);
+ String classPath2 = options.get(OptionName.CLASSPATH);
+ if (classPath1 != null) {
+ if (classPath2 != null) {
+ classPath = classPath1 + File.pathSeparator + classPath2;
+ } else {
+ classPath = classPath1;
+ }
+ } else {
+ classPath = classPath2;
+ }
+ }
+
+ if (outputDirectory == null) {
+ outputDirectory = options.get(OptionName.D);
+ }
+ }
+ }
+
+ /**
+ * Prints {@code diag} with any additional contract code information
+ * available.
+ */
+ @Requires("r != null")
+ protected void printDiagnostic(DiagnosticManager.Report r) {
+ Messager messager = processingEnv.getMessager();
+ if (r.getElement() == null) {
+ messager.printMessage(r.getKind(), r.getMessage(null));
+ } else {
+ messager.printMessage(r.getKind(), r.getMessage(null), r.getElement(),
+ r.getAnnotationMirror(), r.getAnnotationValue());
+ }
+ }
+
+ /**
+ * Dumps the computed Java source files in the dump directory of
+ * Contracts for Java.
+ */
+ @Requires({
+ "types != null",
+ "sources != null",
+ "types.size() == sources.size()"
+ })
+ protected void dumpSources(List<TypeModel> types,
+ List<SyntheticJavaFile> sources) {
+ Iterator<TypeModel> itType = types.iterator();
+ Iterator<SyntheticJavaFile> itFile = sources.iterator();
+ while (itType.hasNext() && itFile.hasNext()) {
+ TypeModel type = itType.next();
+ SyntheticJavaFile file = itFile.next();
+ DebugUtils.dump(type.getName().getBinaryName(),
+ file.getCharContent(true).toString().getBytes(),
+ Kind.SOURCE);
+ }
+ }
+
+ /**
+ * Builds {@link TypeModel} objects from the {@code roots}. The
+ * types built contain contract methods. Helper types are created
+ * for interfaces.
+ */
+ @Requires({
+ "roots != null",
+ "diagnosticManager != null"
+ })
+ @Ensures({
+ "result != null",
+ "result.size() >= roots.size()",
+ "result.size() <= 2 * roots.size()"
+ })
+ protected List<TypeModel> createTypes(Set<TypeElement> roots,
+ DiagnosticManager diagnosticManager) {
+ boolean errors = false;
+
+ /*
+ * Extract all type names that will be part of this compilation
+ * task.
+ */
+ final HashSet<String> knownTypeNames = new HashSet<String>();
+ for (TypeElement r : roots) {
+ ElementScanner6<Void, Void> visitor =
+ new ElementScanner6<Void, Void>() {
+ @Override
+ public Void visitType(TypeElement e, Void p) {
+ knownTypeNames.add(e.getQualifiedName().toString());
+ return super.visitType(e, p);
+ }
+ };
+ r.accept(visitor, null);
+ }
+ /*
+ * Mark annotations inherited from classes compiled in the same
+ * task as weak so we don't generate stubs for them later on. This
+ * prevents name clashes due to erasure.
+ */
+ ArrayList<TypeModel> undecoratedTypes =
+ new ArrayList<TypeModel>(roots.size());
+ for (TypeElement r : roots) {
+ TypeModel type = factory.createType(r, diagnosticManager);
+ ElementScanner annotator =
+ new ElementScanner() {
+ @Override
+ public void visitContractAnnotation(
+ ContractAnnotationModel annotation) {
+ if (annotation.isVirtual()
+ && knownTypeNames.contains(
+ annotation.getOwner().getQualifiedName())) {
+ annotation.setWeakVirtual(true);
+ }
+ }
+ };
+ type.accept(annotator);
+ undecoratedTypes.add(type);
+ }
+ /*
+ * Decorate the type models with contract methods and create
+ * helper types.
+ */
+ ArrayList<TypeModel> types =
+ new ArrayList<TypeModel>(undecoratedTypes.size());
+ for (TypeModel type : undecoratedTypes) {
+ ClassContractCreator creator =
+ new ClassContractCreator(diagnosticManager);
+ type.accept(creator);
+ TypeModel helper = creator.getHelperType();
+ types.add(type);
+ if (helper != null) {
+ types.add(helper);
+ }
+ }
+
+ return types;
+ }
+
+ /**
+ * Returns the set of root elements that contain contracts.
+ * Contracts can have been directly declared as annotations or inherited
+ * through the hierarchy.
+ *
+ * @param annotations the set of annotations to look for
+ * @param roundEnv the environment to get elements from
+ */
+ @Requires("roundEnv != null")
+ @Ensures("result != null")
+ protected Set<TypeElement> getContractedRootElements(
+ RoundEnvironment roundEnv) {
+ Set<? extends Element> allElements = roundEnv.getRootElements();
+ Set<TypeElement> contractedRootElements =
+ new HashSet<TypeElement>(allElements.size());
+
+ ContractFinder cf = new ContractFinder(utils);
+ for (Element e : allElements) {
+ if (e.accept(cf, null)) {
+ contractedRootElements.add(getRootElement(e));
+ }
+ }
+
+ return contractedRootElements;
+ }
+
+ /**
+ * Returns the top-level type element enclosing {@code element}.
+ */
+ @Requires({
+ "element != null",
+ "element.getKind() != ElementKind.PACKAGE",
+ "element.getKind() != ElementKind.OTHER"
+ })
+ @Ensures("element != null")
+ protected static TypeElement getRootElement(Element element) {
+ if (element.getKind().isClass() || element.getKind().isInterface()) {
+ TypeElement type = (TypeElement) element;
+ if (!type.getNestingKind().isNested()) {
+ return type;
+ }
+ }
+
+ return getRootElement(element.getEnclosingElement());
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/AnnotationSourceInfo.java b/src/com/google/java/contract/core/apt/AnnotationSourceInfo.java
new file mode 100644
index 0000000..717b9b5
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/AnnotationSourceInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+
+/**
+ * Source-tracking information, for error reporting.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "element != null",
+ "annotationMirror != null",
+ "annotationValue != null",
+ "code != null",
+ "!code.isEmpty()",
+ "!code.contains(null)"
+})
+public class AnnotationSourceInfo {
+ protected Element element;
+ protected AnnotationMirror annotationMirror;
+ protected AnnotationValue annotationValue;
+ protected List<String> code;
+
+ @Requires({
+ "element != null",
+ "annotationMirror != null",
+ "annotationValue != null",
+ "code != null",
+ "!code.isEmpty()"
+ })
+ @Ensures({
+ "element == getElement()",
+ "annotationMirror == getAnnotationMirror()",
+ "annotationValue == getAnnotationValue()",
+ "code.equals(getCode())"
+ })
+ public AnnotationSourceInfo(Element element,
+ AnnotationMirror annotationMirror, AnnotationValue annotationValue,
+ List<String> code) {
+ this.element = element;
+ this.annotationMirror = annotationMirror;
+ this.annotationValue = annotationValue;
+ this.code = new ArrayList<String>(code);
+ }
+
+ @Ensures("result != null")
+ public AnnotationMirror getAnnotationMirror() {
+ return annotationMirror;
+ }
+
+ @Ensures("result != null")
+ public AnnotationValue getAnnotationValue() {
+ return annotationValue;
+ }
+
+ @Ensures({
+ "result != null",
+ "!result.isEmpty()"
+ })
+ public List<String> getCode() {
+ return code;
+ }
+
+ @Ensures("result != null")
+ public Element getElement() {
+ return element;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ClassContractCreator.java b/src/com/google/java/contract/core/apt/ClassContractCreator.java
new file mode 100644
index 0000000..23ec766
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ClassContractCreator.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import static com.google.java.contract.core.apt.ContractCreation.createContractMethods;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.HelperTypeModel;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.util.ElementScanner;
+
+/**
+ * Element visitor responsible for decorating a {@link TypeModel}
+ * object with class-wide contract code elements. Delegates
+ * method-wide contracts to {@link MethodContractCreator}.
+ *
+ * <p>Contracts are combined together into a single contract method,
+ * which evaluates the contracts. Moreover, each primary annotation
+ * also creates a helper method called a primary contract method.
+ *
+ * <p>A primary contract method only evaluates the current class
+ * invariant, ignoring inherited contracts. It is called by children
+ * of the class to work around access restrictions.
+ *
+ * <p>Interface contracts do not have a primary implementation as
+ * interfaces have no code body of their own. Since there can also
+ * be no access restrictions in interface contracts, they are simply
+ * inlined.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "diagnosticManager != null",
+ "transformer != null"
+})
+public class ClassContractCreator extends ElementScanner {
+ protected DiagnosticManager diagnosticManager;
+
+ protected TypeModel type;
+ protected TypeModel helperType;
+ protected ContractMethodModel invariant;
+
+ protected ContractExpressionTransformer transformer;
+
+ /**
+ * Constructs a new ClassContractCreator.
+ */
+ @Requires("diagnosticManager != null")
+ public ClassContractCreator(DiagnosticManager diagnosticManager) {
+ this.diagnosticManager = diagnosticManager;
+ type = null;
+ helperType = null;
+ invariant = null;
+ transformer = new ContractExpressionTransformer(diagnosticManager, false);
+ }
+
+ public TypeModel getHelperType() {
+ return helperType;
+ }
+
+ @Override
+ public void visitType(TypeModel type) {
+ if (this.type != null) {
+ ClassContractCreator creator =
+ new ClassContractCreator(diagnosticManager);
+ type.accept(creator);
+ if (creator.getHelperType() != null) {
+ this.type.addEnclosedElement(creator.getHelperType());
+ }
+ return;
+ }
+ this.type = type;
+ if (type.getKind() == ElementKind.INTERFACE) {
+ helperType = new HelperTypeModel(type);
+ super.visitType(helperType);
+ } else {
+ super.visitType(type);
+ }
+ }
+
+ @Override
+ public void visitMethod(MethodModel method) {
+ method.accept(new MethodContractCreator(diagnosticManager));
+ }
+
+ @Override
+ public void visitContractAnnotation(ContractAnnotationModel annotation) {
+ if (!annotation.getKind().equals(ElementKind.INVARIANT)) {
+ throw new IllegalArgumentException();
+ }
+ ContractExpressionCreationTrait trait =
+ new ContractExpressionCreationTrait(transformer) {
+ @Override
+ public String getExceptionName() {
+ return "com.google.java.contract.InvariantError";
+ }
+ };
+ invariant = createContractMethods(trait, invariant, annotation);
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractCreation.java b/src/com/google/java/contract/core/apt/ContractCreation.java
new file mode 100644
index 0000000..3afbcb0
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractCreation.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ContractVariance;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.TypeName;
+import com.google.java.contract.core.model.VariableModel;
+import com.google.java.contract.core.util.Elements;
+import com.google.java.contract.core.util.JavaTokenizer.Token;
+import com.google.java.contract.core.util.JavaTokenizer.TokenKind;
+import com.google.java.contract.core.util.JavaUtils;
+import com.google.java.contract.core.util.PushbackTokenizer;
+
+import java.io.StringReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility methods for contract creators.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class ContractCreation {
+ static final String RAISE_METHOD =
+ "com.google.java.contract.core.runtime.ContractRuntime.raise";
+
+ /**
+ * Returns {@code code} with all unqualified or this-qualified
+ * identifiers followed by an open parenthesis rebased to
+ * {@code that}. Unqualified identifiers in {@code whitelist}
+ * are not subject to this change.
+ */
+ @Requires({
+ "code != null",
+ "that != null"
+ })
+ @Ensures("result != null")
+ static String rebaseLocalCalls(String code, String that,
+ Set<String> whitelist) {
+ StringBuilder buffer = new StringBuilder();
+ PushbackTokenizer tokenizer = new PushbackTokenizer(new StringReader(code));
+ boolean qualified = false;
+ while (tokenizer.hasNext()) {
+ Token token = tokenizer.next();
+ if (!qualified && token.kind == TokenKind.WORD
+ && (whitelist == null || !whitelist.contains(token.text))) {
+ if (token.text.equals("this")) {
+ buffer.append("( ");
+ buffer.append(JavaUtils.BEGIN_GENERATED_CODE);
+ buffer.append(that);
+ buffer.append(JavaUtils.END_GENERATED_CODE);
+ buffer.append(" )");
+ } else {
+ if (JavaUtils.lookingAt(tokenizer, "(")) {
+ buffer.append(JavaUtils.BEGIN_GENERATED_CODE);
+ buffer.append(that);
+ buffer.append(".");
+ buffer.append(JavaUtils.END_GENERATED_CODE);
+ buffer.append(token.text);
+ } else {
+ buffer.append(token.text);
+ }
+ }
+ } else {
+ buffer.append(token.text);
+ }
+ qualified = token.text.equals(".");
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Builds a contract method body from the {@code trait}.
+ *
+ * @param contract the contract method to add the clauses to
+ * @param trait the trait to get contract code information from
+ * @param annotation the source of the contract
+ */
+ @Requires({
+ "contract != null",
+ "trait != null",
+ "annotation != null"
+ })
+ static void addContractClauses(ContractMethodModel contract,
+ ContractCreationTrait trait,
+ ContractAnnotationModel annotation) {
+ ContractKind kind = getContractKind(annotation);
+
+ Iterator<String> itCode = trait.getExpressions().iterator();
+ Iterator<String> itMsg = trait.getMessages().iterator();
+ Iterator<String> itComment = trait.getSourceExpressions().iterator();
+ int successVariableCount = 0;
+ int exceptionVariableCount = 0;
+ while (itCode.hasNext()) {
+ StringBuilder buffer = new StringBuilder();
+ String expr = itCode.next();
+ String exprMsg = itMsg.next();
+ String exprComment = itComment.next();
+
+ /*
+ * Evaluate predicate. The success variable is first assigned a
+ * dummy value so as to be able to track the start of the
+ * evaluation in the generated bytecode.
+ */
+ String successVariableName =
+ JavaUtils.SUCCESS_VARIABLE_PREFIX + "$" + successVariableCount++;
+ String exceptionTempVariableName =
+ JavaUtils.EXCEPTION_VARIABLE_PREFIX + "$" + exceptionVariableCount++;
+ String exceptionVariableName =
+ JavaUtils.EXCEPTION_VARIABLE_PREFIX + "$" + exceptionVariableCount++;
+ buffer.append("boolean ");
+ buffer.append(successVariableName);
+ buffer.append(" = false; ");
+ buffer.append("Throwable ");
+ buffer.append(exceptionVariableName);
+ buffer.append(" = null; ");
+ buffer.append("try { ");
+ buffer.append(successVariableName);
+ buffer.append(" = ");
+ buffer.append(JavaUtils.BEGIN_LOCATION_COMMENT);
+ buffer.append(JavaUtils.quoteComment(exprComment));
+ buffer.append(JavaUtils.END_LOCATION_COMMENT);
+ if (!annotation.isVirtual()) {
+ buffer.append(rebaseLocalCalls(expr, JavaUtils.THAT_VARIABLE, null));
+ } else {
+ buffer.append(expr);
+ }
+ buffer.append("; ");
+ buffer.append("} catch(Throwable ");
+ buffer.append(exceptionTempVariableName);
+ buffer.append(") {");
+ buffer.append(exceptionVariableName);
+ buffer.append(" = ");
+ buffer.append(exceptionTempVariableName);
+ buffer.append("; } ");
+
+ /* Handle failure. */
+ buffer.append("if (!(");
+ buffer.append(successVariableName);
+ buffer.append(")) { ");
+ if (kind.getVariance() == ContractVariance.CONTRAVARIANT) {
+ buffer.append("return new ");
+ buffer.append(trait.getExceptionName());
+ buffer.append("(\"");
+ buffer.append(ContractWriter.quoteString(exprMsg));
+ buffer.append("\", ");
+ buffer.append(JavaUtils.ERROR_VARIABLE);
+ buffer.append(", ");
+ buffer.append(exceptionVariableName);
+ buffer.append("); ");
+ } else {
+ buffer.append(RAISE_METHOD);
+ buffer.append("(new ");
+ buffer.append(trait.getExceptionName());
+ buffer.append("(\"");
+ buffer.append(ContractWriter.quoteString(exprMsg));
+ buffer.append("\", ");
+ buffer.append(exceptionVariableName);
+ buffer.append("));");
+ }
+ buffer.append("} ");
+
+ contract.addStatement(buffer.toString());
+ }
+ }
+
+ /**
+ * Builds a contract method body that calls the specified helper
+ * contract method.
+ */
+ @Requires({
+ "helper != null",
+ "annotation != null"
+ })
+ static String getHelperCallCode(MethodModel helper,
+ ContractAnnotationModel annotation) {
+ StringBuilder buffer = new StringBuilder();
+ if (!annotation.isPrimary() && !annotation.isVirtual()) {
+ buffer.append(annotation.getOwner().getQualifiedName()
+ + JavaUtils.HELPER_CLASS_SUFFIX);
+ buffer.append(".");
+ }
+ buffer.append(helper.getSimpleName());
+ buffer.append("(");
+ List<? extends VariableModel> parameters = helper.getParameters();
+ if (!parameters.isEmpty()) {
+ Iterator<? extends VariableModel> it = parameters.iterator();
+ for (;;) {
+ String name = it.next().getSimpleName();
+ if (name.equals(JavaUtils.THAT_VARIABLE)) {
+ buffer.append("this");
+ } else {
+ buffer.append(name);
+ }
+ if (!it.hasNext()) {
+ break;
+ }
+ buffer.append(", ");
+ }
+ }
+ buffer.append(")");
+ return buffer.toString();
+ }
+
+ static ContractKind getContractKind(ContractAnnotationModel annotation) {
+ switch (annotation.getKind()) {
+ case INVARIANT:
+ return ContractKind.INVARIANT;
+ case REQUIRES:
+ return ContractKind.PRE;
+ case ENSURES:
+ return ContractKind.POST;
+ case THROW_ENSURES:
+ return ContractKind.SIGNAL;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Visits the specified trait and creates or augment contract and
+ * helper methods as needed.
+ *
+ * @see #createContractMethod(ContractCreationTrait,ContractMethodModel,ContractAnnotationModel,MethodModel)
+ * @see #createContractHelper(ContractCreationTrait,ContractAnnotationModel)
+ */
+ @Requires({
+ "trait != null",
+ "annotation != null"
+ })
+ static ContractMethodModel createContractMethods(
+ ContractCreationTrait trait, ContractMethodModel contract,
+ ContractAnnotationModel annotation) {
+ if (!trait.visit(annotation)) {
+ return null;
+ }
+ MethodModel helper = createContractHelper(trait, annotation);
+ return createContractMethod(trait, contract, annotation, helper);
+ }
+
+ /**
+ * Returns a new blank contract method, according to the annotation
+ * properties, and adds it to the parent type. Such a method returns
+ * {@code void}, and has no code and no line information.
+ *
+ * @param kind the kind of contract method to create
+ * @param annotation the source of the contract
+ * @param nameSuffix a suffix to append to the method name, or
+ * {@code null}
+ * @return the contract method
+ */
+ @Requires({
+ "kind != null",
+ "annotation != null"
+ })
+ @Ensures("result != null")
+ static ContractMethodModel createBlankContractMethod(
+ ContractKind kind, ContractAnnotationModel annotation,
+ String nameSuffix) {
+ TypeModel type = Elements.getTypeOf(annotation);
+
+ MethodModel contracted = null;
+ if (kind.isMethodContract()) {
+ contracted = (MethodModel) annotation.getEnclosingElement();
+ }
+
+ String name = kind.getNameSpace() + getContractName(kind, contracted);
+ if (nameSuffix != null) {
+ name += nameSuffix;
+ }
+ ContractMethodModel contract =
+ new ContractMethodModel(kind, name, new TypeName("void"), contracted);
+
+ contract.addModifier(ElementModifier.PRIVATE);
+ type.addMember(contract);
+
+ return contract;
+ }
+
+ /**
+ * Adds to the specified contract method, or creates a new one and
+ * adds it to the parent type.
+ *
+ * <p>This method does <em>not</em> visit its trait argument.
+ *
+ * @param trait the trait object used to create the contract
+ * @param contract the contract object to be augmented,
+ * or {@code null}
+ * @param annotation the source of the contract
+ * @param helper a helper method to call or {@code null} if this
+ * contract is direct
+ * @return the contract method
+ */
+ @Requires({
+ "trait != null",
+ "annotation != null",
+ "helper != null"
+ })
+ @Ensures("result != null")
+ static ContractMethodModel createContractMethod(
+ ContractCreationTrait trait, ContractMethodModel contract,
+ ContractAnnotationModel annotation, MethodModel helper) {
+ ContractKind kind = getContractKind(annotation);
+
+ if (contract == null) {
+ contract = createBlankContractMethod(kind, annotation, "");
+ Elements.copyParameters(contract, trait.getInitialParameters());
+
+ if (kind.getVariance() == ContractVariance.CONTRAVARIANT) {
+ contract.setPrologue(trait.getExceptionName() + " "
+ + JavaUtils.ERROR_VARIABLE + " = null;");
+ contract.setEpilogue(RAISE_METHOD + "("
+ + JavaUtils.ERROR_VARIABLE + ");");
+ }
+ }
+ Elements.copyParameters(contract, trait.getExtraParameters());
+
+ if (annotation.isPrimary()) {
+ contract.setSourceInfo(annotation.getSourceInfo());
+ }
+
+ String code = getHelperCallCode(helper, annotation) + ";";
+ if (kind.getVariance() == ContractVariance.CONTRAVARIANT) {
+ code = JavaUtils.ERROR_VARIABLE + " = " + code
+ + "if (" + JavaUtils.ERROR_VARIABLE + " == null) { return; }";
+ }
+ contract.addStatement(code);
+
+ return contract;
+ }
+
+ /**
+ * Returns a new blank primary or mock helper contract method,
+ * according to the annotation properties, and adds it to the parent
+ * type if needed. Such a method returns {@code void} and has no
+ * code and no line information.
+ *
+ * @param kind the kind of contract method to create
+ * @param annotation the source of the contract
+ * @param nameSuffix a suffix to append to the method name, or
+ * {@code null}
+ */
+ @Requires({
+ "kind != null",
+ "annotation != null"
+ })
+ @Ensures("result != null")
+ static MethodModel createBlankContractHelper(
+ ContractKind kind, ContractAnnotationModel annotation,
+ String nameSuffix) {
+ TypeModel type = Elements.getTypeOf(annotation);
+ MethodModel method = null;
+ ContractMethodModel contract = null;
+
+ MethodModel contracted = null;
+ if (kind.isMethodContract()) {
+ contracted = (MethodModel) annotation.getEnclosingElement();
+ }
+
+ TypeName returnType = new TypeName("void");
+ String name = getHelperName(kind, annotation.getOwner(), contracted);
+ if (nameSuffix != null) {
+ name += nameSuffix;
+ }
+ if (annotation.isPrimary()) {
+ contract = new ContractMethodModel(ContractKind.HELPER, name,
+ returnType, contracted);
+
+ contract.setSourceInfo(annotation.getSourceInfo());
+
+ if (!annotation.isVirtual()) {
+ for (TypeName typeParam : type.getTypeParameters()) {
+ contract.addTypeParameter(typeParam);
+ }
+ }
+
+ method = contract;
+ } else {
+ method = new MethodModel(ElementKind.CONTRACT_MOCK, name, returnType);
+ if (contracted != null) {
+ Elements.copyParameters(method, contracted.getParameters());
+ }
+ }
+
+ if (!annotation.isVirtual()) {
+ method.addParameter(
+ new VariableModel(ElementKind.PARAMETER,
+ JavaUtils.THAT_VARIABLE, annotation.getOwner()));
+ }
+
+ if (!annotation.isVirtual()) {
+ method.addModifier(ElementModifier.PUBLIC);
+ method.addModifier(ElementModifier.STATIC);
+ } else {
+ method.addModifier(ElementModifier.PROTECTED);
+ }
+
+ if (annotation.isPrimary()
+ || annotation.isVirtual() && !annotation.isWeakVirtual()) {
+ type.addMember(method);
+ }
+
+ return method;
+ }
+
+ /**
+ * Creates a new primary or mock helper contract method, according
+ * to the annotation properties, and adds it to the parent type if
+ * needed.
+ *
+ * <p>This method does <em>not</em> visit its trait argument.
+ *
+ * @param trait the trait object used to create the contract
+ * @param annotation the source of the contract
+ */
+ @Requires({
+ "trait != null",
+ "annotation != null"
+ })
+ @Ensures("result != null")
+ static MethodModel createContractHelper(ContractCreationTrait trait,
+ ContractAnnotationModel annotation) {
+ ContractKind kind = getContractKind(annotation);
+ MethodModel method = createBlankContractHelper(kind, annotation, null);
+
+ TypeName returnType =
+ new TypeName(kind.getVariance() == ContractVariance.CONTRAVARIANT
+ ? trait.getExceptionName()
+ : "void");
+ method.setReturnType(returnType);
+
+ if (kind.getVariance() == ContractVariance.CONTRAVARIANT) {
+ method.addParameter(
+ new VariableModel(ElementKind.PARAMETER,
+ JavaUtils.ERROR_VARIABLE, returnType));
+ }
+
+ if (annotation.isPrimary()) {
+ method.setSourceInfo(annotation.getSourceInfo());
+ }
+
+ if (method.getKind() == ElementKind.CONTRACT_METHOD) {
+ ContractMethodModel contract = (ContractMethodModel) method;
+
+ Elements.copyParameters(contract, trait.getInitialParameters());
+ Elements.copyParameters(contract, trait.getExtraParameters());
+
+ addContractClauses(contract, trait, annotation);
+ if (kind.getVariance() == ContractVariance.CONTRAVARIANT) {
+ contract.setEpilogue("return null;");
+ }
+
+ if (annotation.isPrimary()) {
+ contract.setLineNumbers(annotation.getLineNumbers());
+ }
+ } else {
+ Elements.copyParameters(method, trait.getInitialMockParameters());
+ Elements.copyParameters(method, trait.getExtraMockParameters());
+ }
+
+ return method;
+ }
+
+ /**
+ * Builds and returns a helper method name.
+ */
+ @Requires({
+ "kind != null",
+ "owner != null"
+ })
+ @Ensures("ClassName.isSimpleName(result)")
+ static String getHelperName(ContractKind kind, ClassName owner,
+ MethodModel contracted) {
+ return kind.getHelperNameSpace() + "$"
+ + owner.getBinaryName().replace('/', '$')
+ + getContractName(kind, contracted);
+ }
+
+ /**
+ * Returns the relative contract name, without the name space part.
+ */
+ @Requires({
+ "kind != null",
+ "!kind.isClassContract() || contracted == null"
+ })
+ @Ensures({
+ "result.isEmpty() " +
+ "|| ClassName.isSimpleName(result) && result.startsWith(\"$\")"
+ })
+ static String getContractName(ContractKind kind, MethodModel contracted) {
+ if (contracted == null) {
+ return "";
+ } else {
+ if (contracted.isConstructor()) {
+ return "$" + contracted.getEnclosingElement().getSimpleName();
+ } else {
+ return "$" + contracted.getSimpleName();
+ }
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractCreationTrait.java b/src/com/google/java/contract/core/apt/ContractCreationTrait.java
new file mode 100644
index 0000000..47daa81
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractCreationTrait.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.VariableModel;
+
+import java.util.List;
+
+/**
+ * A hybrid visitor with query methods that governs the creation of a
+ * given kind of contract.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport(ClassName.class)
+interface ContractCreationTrait {
+ /**
+ * Called before any of the query methods of this object. Queries
+ * between two calls to this method should be consistent.
+ *
+ * @param annotation the annotation to process
+ * @return {@code true} if there was no error
+ */
+ @Requires("annotation != null")
+ public boolean visit(ContractAnnotationModel annotation);
+
+ /**
+ * Returns the list of expressions to be evaluated by the contract.
+ */
+ @Ensures("result != null")
+ public List<String> getExpressions();
+
+ /**
+ * Returns the list of failure messages associated with the
+ * assertions of the contract.
+ */
+ @Ensures("result != null")
+ public List<String> getMessages();
+
+ /**
+ * Returns the list of expressions to be used in error
+ * reporting. This should be a list of unchanged clauses from the
+ * original annotation.
+ */
+ @Ensures("result != null")
+ public List<String> getSourceExpressions();
+
+ /**
+ * Returns the list of extra parameters to add to the end of the
+ * contract method parameter list. These parameters are only added
+ * at creation; they are <em>not</em> added to an existing contract
+ * method.
+ */
+ @Ensures("result != null")
+ public List<? extends VariableModel> getInitialParameters();
+
+ /**
+ * Returns the list of extra parameters to add to the end of the
+ * contract method parameter list. These parameters are added to the
+ * contract method regardless of whether it is new or not, after
+ * parameters returned by
+ * {@link #getInitialParameters(ContractAnnotationModel)},
+ * if applicable.
+ */
+ @Ensures("result != null")
+ public List<? extends VariableModel> getExtraParameters();
+
+ /**
+ * Returns the list of initial extra parameters for mock
+ * definitions.
+ *
+ * @see getInitialParameters()
+ */
+ @Ensures("result != null")
+ public List<? extends VariableModel> getInitialMockParameters();
+
+ /**
+ * Returns the list of extra parameters for mock definitions.
+ *
+ * @see getExtraParameters()
+ */
+ @Ensures("result != null")
+ public List<? extends VariableModel> getExtraMockParameters();
+
+ /**
+ * Returns the name of the exception type to throw when a failure
+ * occurs.
+ */
+ @Ensures("ClassName.isQualifiedName(result)")
+ public String getExceptionName();
+}
diff --git a/src/com/google/java/contract/core/apt/ContractExpressionCreationTrait.java b/src/com/google/java/contract/core/apt/ContractExpressionCreationTrait.java
new file mode 100644
index 0000000..358c92a
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractExpressionCreationTrait.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+
+import java.util.List;
+
+/**
+ * Creation trait for common transformations shared by all kinds of
+ * contracts.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("transformer != null")
+class ContractExpressionCreationTrait
+ extends SimpleContractCreationTrait {
+ protected ContractExpressionTransformer transformer;
+
+ /**
+ * Constructs a new ContractExpressionTransformer using
+ * {@code transformer}.
+ */
+ @Requires("transformer != null")
+ ContractExpressionCreationTrait(
+ ContractExpressionTransformer transformer) {
+ this.transformer = transformer;
+ }
+
+ /**
+ * Checks and transforms {@code code} through {@link #transformer}.
+ *
+ * @see ContractExpressionTransformer#transform(List<String>,List<Long>,Object)
+ */
+ @Requires({
+ "code != null",
+ "lineNumbers != null",
+ "code.size() == lineNumbers.size()"
+ })
+ protected boolean transform(List<String> code, List<Long> lineNumbers,
+ Object sourceInfo) {
+ return transformer.transform(code, lineNumbers, sourceInfo);
+ }
+
+ @Override
+ public boolean visit(ContractAnnotationModel annotation) {
+ this.annotation = annotation;
+ return transform(annotation.getValues(), annotation.getLineNumbers(),
+ annotation.getSourceInfo());
+ }
+
+ @Override
+ public List<String> getExpressions() {
+ return transformer.getTransformedCode();
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java b/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java
new file mode 100644
index 0000000..e4218c0
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.VariableModel;
+import com.google.java.contract.core.util.BalancedTokenizer;
+import com.google.java.contract.core.util.JavaTokenizer.Token;
+import com.google.java.contract.core.util.JavaTokenizer.TokenKind;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A contract expression checker and transformer. An instance of this
+ * class can do basic syntax checking and handle language extensions
+ * supported in contract expression.
+ *
+ * <p>Currently, the following checks are performed:
+ * <ul>
+ * <li>Proper nesting and matching of '()', '[]' and '{}'.
+ * <li>Erroneous ';' in expression.
+ * </ul>
+ *
+ * <p>And the following transformations are applied:
+ * <ul>
+ * <li>Comments are replaced by whitespace.
+ * <li>{@code old()} expressions are extracted and replaced with old
+ * value variable references. (Optional.)
+ * </ul>
+ *
+ * <p>All generated code is marked up with the appropriate tags, as
+ * defined in {@link com.google.java.contract.core.util.JavaUtils}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at Invariant({
+ "diagnosticManager != null",
+ "oldId >= 0",
+ "oldParameters == null || oldParametersCode != null",
+ "oldParameters == null || oldParametersLineNumbers != null",
+ "oldParameters == null || !oldParameters.contains(null)",
+ "oldParameters == null || !oldParametersCode.contains(null)",
+ "oldParameters == null || oldParameters.size() == oldParametersCode.size()",
+ "oldParameters == null " +
+ "|| oldParameters.size() == oldParametersLineNumbers.size()"
+})
+public class ContractExpressionTransformer {
+ private static final String MAGIC_CAST_METHOD =
+ "com.google.java.contract.core.runtime.ContractRuntime.magicCast";
+
+ /**
+ * The diagnostic manager to report errors to.
+ */
+ protected DiagnosticManager diagnosticManager;
+
+ /**
+ * {@code true} if old expressions should be transformed.
+ * If {@code false}, old constructs are ignored.
+ */
+ protected boolean acceptOld;
+
+ /**
+ * The extra parameters needed to hold the extracted old values.
+ */
+ protected List<VariableModel> oldParameters;
+
+ /**
+ * The code corresponding to the old parameters.
+ */
+ protected List<String> oldParametersCode;
+
+ /**
+ * The line numbers corresponding to the old parameters.
+ */
+ protected List<Long> oldParametersLineNumbers;
+
+ /**
+ * The processed code, free of {@code old()} expressions.
+ */
+ protected List<String> newCode;
+
+ /**
+ * Whether the last call to {@link #transform(List<String>,Object)}
+ * was successful.
+ */
+ protected boolean parsed;
+
+ /**
+ * The identifier of the next old variable to allocate.
+ */
+ protected int oldId;
+
+ /**
+ * Constructs a new ContractExpressionTransformer.
+ *
+ * @param diagnosticManager manager to report errors to
+ * @param acceptOld whether old expressions are recognized
+ */
+ public ContractExpressionTransformer(DiagnosticManager diagnosticManager,
+ boolean acceptOld) {
+ this.diagnosticManager = diagnosticManager;
+ this.acceptOld = acceptOld;
+ oldParameters = null;
+ oldParametersCode = null;
+ oldParametersLineNumbers = null;
+ newCode = null;
+ parsed = false;
+ oldId = 0;
+ }
+
+ public void setAcceptOld(boolean acceptOld) {
+ this.acceptOld = acceptOld;
+ }
+
+ @Requires({
+ "currentBuffer != null",
+ "tokenizer != null",
+ "token != null"
+ })
+ private void transformCommon(StringBuilder currentBuffer,
+ BalancedTokenizer tokenizer, Token token) {
+ switch (token.kind) {
+ case COMMENT:
+ int length = token.text.length();
+ for (int i = 0; i < length; ++i) {
+ currentBuffer.append(" ");
+ }
+ break;
+ default:
+ currentBuffer.append(token.text);
+ }
+ }
+
+ /**
+ * Checks and transforms {@code code}. If successful, results are
+ * stored in this instance and can be queried using the appropriate
+ * methods.
+ *
+ * @param code the list of contract expressions to parse
+ * @param lineNumbers line numbers associated with {@code code}
+ * @param sourceInfo optional source information
+ * @return {@code true} if there was no errors
+ */
+ @Requires({
+ "code != null",
+ "lineNumbers != null",
+ "code.size() == lineNumbers.size()"
+ })
+ @Ensures({
+ "result == canQueryResults()",
+ "!result || newCode.size() == code.size()"
+ })
+ @SuppressWarnings("fallthrough")
+ public boolean transform(List<String> code, List<Long> lineNumbers,
+ Object sourceInfo) {
+ oldParameters = new ArrayList<VariableModel>();
+ oldParametersCode = new ArrayList<String>();
+ oldParametersLineNumbers = new ArrayList<Long>();
+ newCode = new ArrayList<String>();
+ parsed = true;
+
+ Iterator<Long> iterLineNumber = lineNumbers.iterator();
+
+ code:
+ for (String expr : code) {
+ Long lineNumber = iterLineNumber.hasNext() ? iterLineNumber.next() : null;
+
+ BalancedTokenizer tokenizer =
+ new BalancedTokenizer(new StringReader(expr));
+ int currentLevel = 0;
+ int newLevel = 0;
+
+ StringBuilder buffer = new StringBuilder();
+
+ StringBuilder oldBuffer = null;
+ String oldName = null;
+ int oldContext = -1;
+
+ while (tokenizer.hasNext()) {
+ Token token = tokenizer.next();
+ newLevel = tokenizer.getCurrentLevel();
+
+ StringBuilder currentBuffer = oldBuffer != null ? oldBuffer : buffer;
+
+ /* Unexpected ';' error. */
+ if (newLevel == 0 && token.text.equals(";")) {
+ diagnosticManager.error("'\"' expected",
+ expr, token.offset, token.offset, token.offset,
+ sourceInfo);
+ parsed = false;
+ continue code;
+ }
+
+ /* old expressions. */
+ if (oldBuffer != null) {
+ if (newLevel == oldContext) {
+ /* End of old expression. */
+ String oldExpr = oldBuffer.toString();
+ oldParameters.add(
+ new VariableModel(ElementKind.PARAMETER, oldName,
+ new ClassName("java/lang/Object")));
+ oldParametersCode.add(oldExpr);
+ oldParametersLineNumbers.add(lineNumber);
+
+ /* Pad buffer (for error reporting purposes). */
+ buffer.append("( ");
+
+ /* Replace old expression in original expression. */
+ buffer.append(JavaUtils.BEGIN_GENERATED_CODE);
+ buffer.append(MAGIC_CAST_METHOD);
+ buffer.append("(");
+ buffer.append(oldName);
+ buffer.append(", ");
+ buffer.append("true ? null : ");
+ buffer.append(JavaUtils.END_GENERATED_CODE);
+ buffer.append(oldExpr);
+ buffer.append(JavaUtils.BEGIN_GENERATED_CODE);
+ buffer.append(")");
+ buffer.append(JavaUtils.END_GENERATED_CODE);
+
+ /* Pad buffer (for error reporting purposes). */
+ buffer.append(")");
+
+ /* Exit old context. */
+ oldBuffer = null;
+ oldContext = -1;
+ } else {
+ switch (token.kind) {
+ case WORD:
+ if (token.text.equals("old")) {
+ diagnosticManager.error("nested old expression",
+ expr, token.offset, token.offset, token.offset,
+ sourceInfo);
+ parsed = false;
+ continue code;
+ }
+ oldBuffer.append(token.text);
+ break;
+ default:
+ transformCommon(oldBuffer, tokenizer, token);
+ }
+ }
+ } else {
+ switch (token.kind) {
+ case WORD:
+ if (acceptOld && token.text.equals("old")) {
+ /* Start of old expression. */
+ Token afterOld = null;
+ if (!tokenizer.hasNext()
+ || !((afterOld = tokenizer.next()).text.equals("(")
+ || (afterOld.kind == TokenKind.SPACE
+ && tokenizer.hasNext()
+ && tokenizer.next().text.equals("(")))) {
+ int errorPos = afterOld != null ? afterOld.offset
+ : tokenizer.getCurrentOffset();
+ diagnosticManager.error("'(' expected",
+ expr, errorPos, errorPos, errorPos,
+ sourceInfo);
+ parsed = false;
+ continue code;
+ }
+
+ /* Compute new old variable name. */
+ oldName = JavaUtils.OLD_VARIABLE_PREFIX + oldId++;
+
+ /* Enter old context. */
+ if (afterOld.kind == TokenKind.SPACE) {
+ oldBuffer = new StringBuilder(afterOld.text);
+ } else {
+ oldBuffer = new StringBuilder();
+ }
+ oldContext = currentLevel;
+ break;
+ }
+ /* Fall through. */
+ default:
+ transformCommon(buffer, tokenizer, token);
+ }
+ }
+
+ currentLevel = newLevel;
+ }
+
+ /* Parse errors. */
+ if (tokenizer.hasErrors()) {
+ int errorPos = tokenizer.getCurrentOffset();
+ diagnosticManager.error(tokenizer.getErrorMessage(),
+ expr, errorPos, errorPos, errorPos, sourceInfo);
+ parsed = false;
+ continue code;
+ }
+ newCode.add(buffer.toString());
+ }
+
+ return parsed;
+ }
+
+ /**
+ * Returns {@code true} if results are ready to be queried.
+ */
+ public boolean canQueryResults() {
+ return parsed;
+ }
+
+ @Ensures("result >= 0")
+ public int getNextOldId() {
+ return oldId;
+ }
+
+ @Requires("canQueryResults()")
+ @Ensures({
+ "result != null",
+ "result.size() == getOldParametersCode().size()",
+ "result.size() == getOldParametersLineNumbers().size()"
+ })
+ public List<VariableModel> getOldParameters() {
+ return oldParameters;
+ }
+
+ @Requires("canQueryResults()")
+ @Ensures({
+ "result != null",
+ "result.size() == getOldParameters().size()",
+ "result.size() == getOldParametersLineNumbers().size()"
+ })
+ public List<String> getOldParametersCode() {
+ return oldParametersCode;
+ }
+
+ @Requires("canQueryResults()")
+ @Ensures({
+ "result != null",
+ "result.size() == getOldParameters().size()",
+ "result.size() == getOldParametersCode().size()"
+ })
+ public List<Long> getOldParametersLineNumbers() {
+ return oldParametersLineNumbers;
+ }
+
+ @Requires("canQueryResults()")
+ @Ensures("result != null")
+ public List<String> getTransformedCode() {
+ return newCode;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractFinder.java b/src/com/google/java/contract/core/apt/ContractFinder.java
new file mode 100644
index 0000000..a352515
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractFinder.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.runtime.BlacklistManager;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementScanner6;
+
+/**
+ * Recursively scans the type tree looking for contract annotations. Results
+ * are cached so there's no need to scan the same element twice.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at Invariant("this.contractedElements != null")
+public class ContractFinder
+ extends ElementScanner6<Boolean, Void> {
+ private FactoryUtils utils;
+ private Map<TypeElement, Boolean> contractedElements;
+ private BlacklistManager blackList;
+
+ @Requires("utils != null")
+ public ContractFinder(FactoryUtils utils) {
+ this.utils = utils;
+ this.contractedElements = new HashMap<TypeElement, Boolean>();
+ this.blackList = BlacklistManager.getInstance();
+ }
+
+ @Override
+ @Ensures("result != null")
+ public Boolean visitType(TypeElement e, Void v) {
+ /* The current element has already been analyzed. */
+ if (contractedElements.containsKey(e)) {
+ return contractedElements.get(e);
+ }
+
+ /* The element is in the blacklist, do not scan. */
+ if (blackList.isIgnored(e.getQualifiedName().toString())) {
+ contractedElements.put(e, Boolean.FALSE);
+ return Boolean.FALSE;
+ }
+
+ /*
+ * Before searching for contracts, add the element to the visited cache.
+ * This is important to avoid recursion (e.g. when an enclosed element
+ * extends the enclosing one).
+ */
+ contractedElements.put(e, Boolean.FALSE);
+ Boolean contracted = isContractedType(e);
+ contractedElements.put(e, contracted);
+ return contracted;
+ }
+
+ @Override
+ @Ensures("result != null")
+ public Boolean visitExecutable(ExecutableElement e, Void p) {
+ return hasContracts(e.getAnnotationMirrors());
+ }
+
+ @Override
+ @Ensures("result != null")
+ public Boolean visitVariable(VariableElement e, Void p) {
+ /* Variables do not have contracts. */
+ return Boolean.FALSE;
+ }
+
+ @Override
+ @Ensures("result != null")
+ public Boolean visitPackage(PackageElement e, Void p) {
+ /* Packages do not have contracts. */
+ return Boolean.FALSE;
+ }
+
+ @Override
+ @Ensures("result != null")
+ public Boolean visitTypeParameter(TypeParameterElement e, Void p) {
+ /* Type parameters do not have contracts. */
+ return Boolean.FALSE;
+ }
+
+ /**
+ * Scans a {@code TypeElement} looking for contracts.
+ * If contracts are not found on the type itself, scans its enclosed elements,
+ * implemented interfaces and superclass.
+ *
+ * @param e the element to be scanned
+ * @return whether the element contains contracts
+ */
+ @Requires("e != null")
+ @Ensures("result != null")
+ private Boolean isContractedType(TypeElement e) {
+ Boolean result = Boolean.FALSE;
+
+ /* The current element has contracts on its annotations. */
+ result = hasContracts(e.getAnnotationMirrors());
+
+ /* Checks superclass. */
+ if (!result) {
+ Element superclass =
+ utils.typeUtils.asElement(e.getSuperclass());
+ if (superclass != null) {
+ /* Scans up in the syntax tree looking for contract annotations. */
+ result = superclass.accept(this, null);
+ }
+ }
+
+ /* Checks interfaces. */
+ if (!result) {
+ Collection<? extends TypeMirror> interfaces = e.getInterfaces();
+ for (TypeMirror i : interfaces) {
+ Element iface = utils.typeUtils.asElement(i);
+ result = iface.accept(this, null);
+ if (result) {
+ break;
+ }
+ }
+ }
+
+ /* Scan the enclosed elements of this. */
+ if (!result) {
+ List<? extends Element> enclosed = e.getEnclosedElements();
+ for (Element element : enclosed) {
+ result = element.accept(this, null);
+ if (result) {
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Requires("annotations != null")
+ @Ensures("result != null")
+ private Boolean hasContracts(
+ Collection<? extends AnnotationMirror> annotations) {
+ for (AnnotationMirror a : annotations) {
+ if (utils.isContractAnnotation(a)) {
+ return Boolean.TRUE;
+ }
+ }
+ return Boolean.FALSE;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractJavaCompiler.java b/src/com/google/java/contract/core/apt/ContractJavaCompiler.java
new file mode 100644
index 0000000..c9b26b6
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractJavaCompiler.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+/**
+ * A compiler that handles generated contract source files.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+ at Invariant({
+ "javaCompiler != null",
+ "fileManager != null"
+})
+public class ContractJavaCompiler {
+ /**
+ * The compiler options passed to the internal compiler.
+ */
+ protected static final List<String> OPTIONS =
+ Arrays.asList(
+ "-g:source,vars", /* Source file name, for debug attributes. */
+ "-proc:none", /* No further annotations to process. */
+ "-implicit:none" /* No class files for implicit dependencies. */
+ );
+
+ /**
+ * Internal compiler to delegate to.
+ */
+ protected JavaCompiler javaCompiler;
+
+ protected ContractJavaFileManager fileManager;
+
+ public ContractJavaCompiler(String sourcePath, String classPath,
+ String outputDirectory)
+ throws IOException {
+ javaCompiler = ToolProvider.getSystemJavaCompiler();
+ if (javaCompiler == null) {
+ throw new IOException("no system JavaCompiler found; "
+ + "are you using a JRE instead of a JDK?");
+ }
+
+ fileManager = new ContractJavaFileManager(
+ javaCompiler.getStandardFileManager(null, null, null));
+
+ if (sourcePath != null) {
+ setPath(StandardLocation.SOURCE_PATH, sourcePath);
+ }
+ if (classPath != null) {
+ setPath(StandardLocation.CLASS_PATH, classPath);
+ }
+ if (outputDirectory != null) {
+ setClassOutputDirectory(outputDirectory);
+ }
+ }
+
+ /**
+ * Returns a new compilation task.
+ */
+ @Requires({
+ "files != null",
+ "diagnostics != null"
+ })
+ @Ensures("result != null")
+ public CompilationTask getTask(List<? extends JavaFileObject> files,
+ DiagnosticListener<JavaFileObject> diagnostics) {
+ return javaCompiler.getTask(null, fileManager, diagnostics,
+ OPTIONS, null, files);
+ }
+
+ @Requires({
+ "location != null",
+ "path != null"
+ })
+ protected void setPath(Location location, String path) throws IOException {
+ String[] parts = path.split(Pattern.quote(File.pathSeparator));
+ ArrayList<File> dirs = new ArrayList<File>(parts.length);
+ for (String part : parts) {
+ dirs.add(new File(part));
+ }
+ fileManager.setLocation(location, dirs);
+ }
+
+ @Requires("outputDirectory != null")
+ protected void setClassOutputDirectory(String outputDirectory)
+ throws IOException {
+ fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
+ Collections.singletonList(new File(outputDirectory)));
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractJavaFileManager.java b/src/com/google/java/contract/core/apt/ContractJavaFileManager.java
new file mode 100644
index 0000000..0d4ab4d
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractJavaFileManager.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.util.Elements;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+
+/**
+ * A file manager that handles output (class) files from contract
+ * compilation. Class files are written in the configured class output
+ * directory (usually alongside other class files).
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public class ContractJavaFileManager
+ extends ForwardingJavaFileManager<StandardJavaFileManager> {
+ /**
+ * An output file to a plain contract class file.
+ */
+ @Invariant("file != null")
+ protected class SimpleOutputJavaFileObject extends SimpleJavaFileObject {
+ protected FileObject file;
+
+ @Requires({
+ "binaryName != null",
+ "file != null"
+ })
+ public SimpleOutputJavaFileObject(String binaryName, FileObject file) {
+ super(Elements.getUriForClass(binaryName, Kind.CLASS), Kind.CLASS);
+ this.file = file;
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return file.openOutputStream();
+ }
+ }
+
+ /**
+ * Constructs a new ContractJavaFileManager writing files to
+ * {@code fileManager}.
+ */
+ @Requires("fileManager != null")
+ public ContractJavaFileManager(StandardJavaFileManager fileManager) {
+ super(fileManager);
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location,
+ String className, Kind kind, FileObject sibling)
+ throws IOException {
+ String binaryName = className.replace('.', '/');
+ String relativeName = ClassName.getRelativeName(className);
+ if (relativeName.endsWith(JavaUtils.HELPER_CLASS_SUFFIX)) {
+ relativeName += Kind.CLASS.extension;
+ } else {
+ relativeName += JavaUtils.CONTRACTS_EXTENSION;
+ }
+ FileObject file =
+ fileManager.getFileForOutput(location,
+ ClassName.getPackageName(className),
+ relativeName, sibling);
+ return new SimpleOutputJavaFileObject(binaryName, file);
+ }
+
+ /**
+ * Returns a list of paths associated with {@code location}, or
+ * {@code null}.
+ */
+ @Requires("location != null")
+ public List<? extends File> getLocation(Location location) {
+ Iterable<? extends File> path = fileManager.getLocation(location);
+ if (path == null) {
+ return null;
+ }
+
+ ArrayList<File> locations = new ArrayList<File>();
+ for (File entry : path) {
+ locations.add(entry);
+ }
+ return locations;
+ }
+
+ /**
+ * Sets the list of paths associated with {@code location}.
+ *
+ * @param location the affected location
+ * @param path a list of paths, or {@code null} to reset to default
+ */
+ @Requires("location != null")
+ public void setLocation(Location location, List<? extends File> path)
+ throws IOException {
+ fileManager.setLocation(location, path);
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/ContractWriter.java b/src/com/google/java/contract/core/apt/ContractWriter.java
new file mode 100644
index 0000000..80a5d36
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/ContractWriter.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.TypeName;
+import com.google.java.contract.core.model.VariableModel;
+import com.google.java.contract.core.util.ElementScanner;
+import com.google.java.contract.core.util.Elements;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * An element visitor that writes the contract Java source associated
+ * with a given {@link Type} as Java source code to an output stream.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant({
+ "getLineNumberMap() != null",
+ "Iterables.all(getLineNumberMap().keySet(), Predicates.between(1L, null))",
+ "output != null",
+ "lineNumber >= 1"
+})
+public class ContractWriter extends ElementScanner {
+ private static final List<String> numericTypes =
+ Arrays.asList("char", "byte", "short", "int", "long", "float", "double");
+
+ private static final String CONTRACT_METHOD_SIGNATURE =
+ "com.google.java.contract.core.agent.ContractMethodSignature";
+ private static final String CONTRACT_KIND =
+ "com.google.java.contract.core.model.ContractKind";
+
+ private static final Pattern VARIADIC_REGEX =
+ Pattern.compile("\\[\\p{javaWhitespace}*\\]\\p{javaWhitespace}*$");
+
+ protected boolean debugTrace;
+
+ protected ByteArrayOutputStream output;
+
+ protected long lineNumber;
+
+ /**
+ * The resulting mapping between contract annotations and generated
+ * line numbers.
+ */
+ protected Map<Long, Object> lineNumberMap;
+
+ /**
+ * {@code true} if this visitor is currently visiting the root
+ * (top-level) class definition.
+ */
+ protected boolean isRootClass;
+
+ protected TypeModel type;
+
+ protected ContractWriter() {
+ this(false);
+ }
+
+ protected ContractWriter(boolean debugTrace) {
+ this.debugTrace = debugTrace;
+ output = new ByteArrayOutputStream();
+ lineNumber = 1;
+ lineNumberMap = new HashMap<Long, Object>();
+ isRootClass = true;
+ type = null;
+ }
+
+ @Requires("parent != null")
+ protected ContractWriter(ContractWriter parent) {
+ debugTrace = parent.debugTrace;
+ output = parent.output;
+ lineNumber = parent.lineNumber;
+ lineNumberMap = parent.lineNumberMap;
+ isRootClass = false;
+ type = null;
+ }
+
+ /**
+ * Returns a default value string of the specified type.
+ */
+ @Requires("type != null")
+ @Ensures("result != null")
+ protected static String getDefaultValue(TypeName type) {
+ String name = type.getDeclaredName();
+ if (name.equals("boolean")) {
+ return "false";
+ } else if (numericTypes.contains(name)) {
+ return "(" + name + ")0";
+ } else {
+ return "(" + name + ")null";
+ }
+ }
+
+ @Requires("str != null")
+ protected void append(String str) {
+ try {
+ output.write(str.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Ensures("lineNumber == old(lineNumber) + 1")
+ protected void appendEndOfLine() {
+ output.write('\n');
+ ++lineNumber;
+ }
+
+ @Requires("info != null")
+ @Ensures("lineNumber == old(lineNumber) + 1")
+ protected void appendEndOfLine(Object info) {
+ lineNumberMap.put(lineNumber, info);
+ appendEndOfLine();
+ }
+
+ @Requires({
+ "list != null",
+ "separator != null"
+ })
+ private void appendJoin(Collection<?> list, String separator) {
+ if (list.isEmpty()) {
+ return;
+ }
+
+ Iterator<?> it = list.iterator();
+ for (;;) {
+ append(it.next().toString());
+ if (!it.hasNext()) {
+ break;
+ }
+ append(separator);
+ }
+ }
+
+ @Requires("signature != null")
+ private void appendGenericSignature(List<? extends TypeName> signature) {
+ if (!signature.isEmpty()) {
+ append("<");
+ appendJoin(signature, ", ");
+ append(">");
+ }
+ }
+
+ @Requires("modifiers != null")
+ private void appendModifiers(EnumSet<ElementModifier> modifiers) {
+ List<ElementModifier> list = new ArrayList<ElementModifier>(modifiers);
+ Collections.sort(list);
+ appendJoin(list, " ");
+ }
+
+ @Requires("rootType != null")
+ private void appendPackageDeclaration(TypeModel rootType) {
+ String packageName =
+ ClassName.getPackageName(rootType.getName().getSemiQualifiedName());
+ if (!packageName.isEmpty()) {
+ append("package ");
+ append(packageName);
+ append(";");
+ appendEndOfLine();
+ }
+ }
+
+ @Requires({
+ "rootType != null",
+ "rootType.getEnclosingElement() == null"
+ })
+ private void appendImportStatements(TypeModel rootType) {
+ for (String importName : rootType.getImportNames()) {
+ append("import ");
+ append(importName);
+ append(";");
+ appendEndOfLine();
+ }
+ }
+
+ @Requires("variable != null")
+ private void appendVariableDeclaration(VariableModel variable) {
+ appendVariableDeclaration(variable, null);
+ }
+
+ @Requires("variable != null")
+ private void appendVariableDeclaration(VariableModel variable,
+ String typeNameOverride) {
+ appendModifiers(variable.getModifiers());
+ append(" ");
+ if (typeNameOverride == null) {
+ append(variable.getType().getDeclaredName());
+ } else {
+ append(typeNameOverride);
+ }
+ append(" ");
+ append(variable.getSimpleName());
+ }
+
+ @Requires("method != null")
+ private void appendMethodDeclaration(MethodModel method) {
+ EnumSet<ElementModifier> modifiers = method.getModifiers();
+ if (type.getKind().isInterfaceType()) {
+ modifiers.remove(ElementModifier.ABSTRACT);
+ }
+ appendModifiers(modifiers);
+ append(" ");
+ appendGenericSignature(method.getTypeParameters());
+
+ if (method.isConstructor()) {
+ append(" ");
+ append(method.getEnclosingElement().getSimpleName());
+ } else {
+ append(" ");
+ append(method.getReturnType().getDeclaredName());
+ append(" ");
+ append(method.getSimpleName());
+ }
+
+ append("(");
+ Iterator<? extends VariableModel> it = method.getParameters().iterator();
+ if (it.hasNext()) {
+ VariableModel param = it.next();
+ while (it.hasNext()) {
+ appendVariableDeclaration(param);
+ append(", ");
+ param = it.next();
+ }
+ if (!method.isVariadic()) {
+ appendVariableDeclaration(param);
+ } else {
+ String variadicTypeName =
+ VARIADIC_REGEX.matcher(param.getType().getDeclaredName())
+ .replaceFirst("...");
+ appendVariableDeclaration(param, variadicTypeName);
+ }
+ }
+ append(")");
+
+ Set<? extends TypeName> exceptions = method.getExceptions();
+ if (exceptions.size() != 0) {
+ append(" throws ");
+ appendJoin(exceptions, ", ");
+ }
+ }
+
+ @Requires({
+ "method != null",
+ "method.isConstructor()"
+ })
+ private void appendConstructorCode(MethodModel method) {
+ TypeModel parent = Elements.getTypeOf(method);
+ List<? extends TypeName> superArguments = parent.getSuperArguments();
+ if (!superArguments.isEmpty()) {
+ append("super(");
+ Iterator<? extends TypeName> it = superArguments.iterator();
+ for (;;) {
+ append(getDefaultValue(it.next()));
+ if (!it.hasNext()) {
+ break;
+ }
+ append(", ");
+ }
+ append(");");
+ }
+ }
+
+ @Requires({
+ "method != null",
+ "!method.isConstructor()"
+ })
+ private void appendNormalMethodCode(MethodModel method) {
+ TypeName returnType = method.getReturnType();
+ if (!returnType.getDeclaredName().equals("void")) {
+ append("return ");
+ append(getDefaultValue(returnType));
+ append(";");
+ }
+ }
+
+ @Requires("contract != null")
+ private void appendContractSignature(ContractMethodModel contract) {
+ append("@");
+ append(CONTRACT_METHOD_SIGNATURE);
+ append("(");
+
+ append("kind = ");
+ append(CONTRACT_KIND);
+ append(".");
+ append(contract.getContractKind().name());
+
+ int id = contract.getId();
+ if (id != -1) {
+ append(", id = ");
+ append(Integer.toString(id));
+ }
+
+ MethodModel contracted = contract.getContractedMethod();
+ if (contracted != null) {
+ append(", target = \"");
+ append(contracted.getSimpleName());
+ append("\"");
+ }
+
+ List<Long> lineNumbers = contract.getLineNumbers();
+ if (lineNumbers != null && !lineNumbers.isEmpty()) {
+ append(", lines = { ");
+ Iterator<Long> it = lineNumbers.iterator();
+ for (;;) {
+ Long lineNumber = it.next();
+ append(Long.toString(lineNumber == null ? -1 : lineNumber));
+ if (!it.hasNext()) {
+ break;
+ }
+ append(", ");
+ }
+ append(" }");
+ }
+
+ append(")");
+ appendEndOfLine();
+ }
+
+ @Override
+ public void visitVariable(VariableModel variable) {
+ if (variable.getKind() == ElementKind.CONSTANT) {
+ /* Handled in visitType(). */
+ return;
+ }
+
+ appendVariableDeclaration(variable);
+ if (variable.getModifiers().contains(ElementModifier.FINAL)) {
+ append(" = ");
+ String defaultValue = getDefaultValue(variable.getType());
+ /*
+ * Append dummy check to prevent the compiler from substituting the field
+ * for its value on contract-checking methods.
+ */
+ append("\"dummy\".equals(\"dummy\") ? ");
+ append(defaultValue);
+ append(":");
+ append(defaultValue);
+ }
+ append(";");
+ appendEndOfLine();
+ }
+
+ @Override
+ public void visitContractMethod(ContractMethodModel contract) {
+ appendContractSignature(contract);
+ appendMethodDeclaration(contract);
+ append(" {");
+ appendEndOfLine();
+
+ Object info = contract.getSourceInfo();
+ if (debugTrace && contract.getContractKind() == ContractKind.HELPER) {
+ append("com.google.java.contract.core.util.DebugUtils.contractInfo(");
+ append("\"checking contract: ");
+ append(quoteString(((TypeModel) contract.getEnclosingElement())
+ .getName().getQualifiedName()));
+ append(".");
+ append(quoteString(contract.getSimpleName()));
+ if (info instanceof AnnotationSourceInfo) {
+ AnnotationSourceInfo sourceInfo = (AnnotationSourceInfo) info;
+ append(": ");
+ append(quoteString(sourceInfo.getAnnotationValue().toString()));
+ }
+ append("\");");
+ appendEndOfLine();
+ }
+ append(contract.getCode());
+ if (info == null) {
+ appendEndOfLine();
+ } else {
+ appendEndOfLine(info);
+ }
+
+ append("}");
+ appendEndOfLine();
+ }
+
+ @Override
+ public void visitMethod(MethodModel method) {
+ /* Enum constructors are handled in visitType(). */
+ if (type.getKind() == ElementKind.ENUM
+ && method.getSimpleName().equals("<init>")) {
+ return;
+ }
+
+ appendMethodDeclaration(method);
+ if (type.getKind().isInterfaceType()
+ || method.getModifiers().contains(ElementModifier.ABSTRACT)) {
+ append(";");
+ } else {
+ append(" {");
+ appendEndOfLine();
+ if (method.isConstructor()) {
+ appendConstructorCode(method);
+ } else {
+ appendNormalMethodCode(method);
+ }
+ appendEndOfLine();
+ append("}");
+ }
+ appendEndOfLine();
+ }
+
+ @Override
+ public void visitType(TypeModel type) {
+ if (this.type != null) {
+ ContractWriter subwriter = new ContractWriter(this);
+ subwriter.visitType(type);
+ lineNumber = subwriter.lineNumber;
+ return;
+ }
+ this.type = type;
+
+ /* Package. */
+ if (isRootClass) {
+ appendPackageDeclaration(type);
+ appendImportStatements(type);
+ }
+
+ /* Type and name. */
+ appendTypeDeclaration(type);
+
+ /* Superclass. */
+ appendSuperclass(type);
+
+ /* Interfaces. */
+ appendInterfaces(type);
+
+ /* Body. */
+ append(" {");
+ appendEndOfLine();
+
+ if (type.getKind() == ElementKind.ENUM) {
+ appendEnumSkeleton(type);
+ }
+
+ /* Members. */
+ scan(type.getEnclosedElements());
+
+ /* End of type. */
+ append("}");
+ appendEndOfLine();
+ }
+
+ @Requires("kind != null")
+ private String getKeywordForType(ElementKind kind) {
+ String keyword = null;
+ switch (type.getKind()) {
+ case CLASS:
+ keyword = "class";
+ break;
+ case ENUM:
+ keyword = "enum";
+ break;
+ case INTERFACE:
+ keyword = "interface";
+ break;
+ case ANNOTATION_TYPE:
+ keyword = "@interface";
+ break;
+ }
+ return keyword;
+ }
+
+ /**
+ * Adds enum constants and a dummy constructor.
+ */
+ @Requires("type != null")
+ private void appendEnumSkeleton(TypeModel type) {
+ /* Enum constants. */
+ List<? extends VariableModel> constants =
+ Elements.filter(type.getEnclosedElements(), VariableModel.class,
+ ElementKind.CONSTANT);
+ Iterator<? extends VariableModel> it = constants.iterator();
+ if (it.hasNext()) {
+ for (;;) {
+ append(it.next().getSimpleName());
+ if (!it.hasNext()) {
+ break;
+ }
+ append(", ");
+ }
+ }
+ append(";");
+ appendEndOfLine();
+
+ /* Enum dummy constructor. */
+ append("private ");
+ append(type.getSimpleName());
+ append("() {");
+ appendEndOfLine();
+ append("}");
+ appendEndOfLine();
+ }
+
+ /**
+ * Adds the type declaration information, including modifiers, name and
+ * generic signature.
+ */
+ private void appendTypeDeclaration(TypeModel type) {
+ String keyword = getKeywordForType(type.getKind());
+ if (keyword == null)
+ throw new IllegalArgumentException();
+
+ /* Type modifiers. */
+ EnumSet<ElementModifier> modifiers = type.getModifiers();
+ switch(type.getKind()) {
+ case INTERFACE:
+ modifiers.remove(ElementModifier.ABSTRACT);
+ break;
+ case ANNOTATION_TYPE:
+ modifiers.remove(ElementModifier.ABSTRACT);
+ modifiers.remove(ElementModifier.STATIC);
+ break;
+ }
+
+ appendModifiers(modifiers);
+ append(" ");
+ append(keyword);
+ append(" ");
+
+ /* Type name. */
+ String printName = type.getSimpleName();
+ append(printName);
+
+ /* Generic parameters. */
+ appendGenericSignature(type.getTypeParameters());
+ }
+
+ /**
+ * Adds superclass information if needed.
+ */
+ @Requires("type != null")
+ private void appendSuperclass(TypeModel type) {
+ if (type.getKind() != ElementKind.ENUM
+ && type.getSuperclass() != null) {
+ append(" extends ");
+ append(type.getSuperclass().getDeclaredName());
+ }
+ }
+
+ /**
+ * Appends information about the interfaces implemented by this type.
+ * All annotations implicitly implement
+ * {@code java.lang.annotation.Annotation}, but this can't be explicitly
+ * declared on the mock.
+ */
+ @Requires({
+ "type != null",
+ "type.getKind() != ElementKind.ANNOTATION_TYPE" +
+ "|| type.getInterfaces().size() == 1"
+ })
+ private void appendInterfaces(TypeModel type) {
+ final ElementKind kind = type.getKind();
+ if (kind != ElementKind.ANNOTATION_TYPE) {
+ Set<? extends ClassName> interfaces = type.getInterfaces();
+ if (interfaces.size() != 0) {
+ if (kind == ElementKind.INTERFACE) {
+ append(" extends ");
+ } else {
+ append(" implements ");
+ }
+ appendJoin(interfaces, ", ");
+ }
+ }
+ }
+
+ /**
+ * Backslash-quotes the specified string for inclusion in source
+ * code.
+ */
+ @Requires("s != null")
+ @Ensures("result != null")
+ public static String quoteString(String s) {
+ return s.replace("\\", "\\\\").replace("\"", "\\\"");
+ }
+
+ public Map<Long, ?> getLineNumberMap() {
+ return lineNumberMap;
+ }
+
+ @Ensures("result != null")
+ public byte[] toByteArray() {
+ return output.toByteArray();
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/DiagnosticManager.java b/src/com/google/java/contract/core/apt/DiagnosticManager.java
new file mode 100644
index 0000000..2899b22
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/DiagnosticManager.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2010, 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.JavaUtils;
+import com.google.java.contract.core.util.SyntheticJavaFile;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+
+/**
+ * A collection of diagnostic messages with facilities to manage
+ * messaging and error reporting.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class DiagnosticManager
+ implements DiagnosticListener<JavaFileObject>,
+ Iterable<DiagnosticManager.Report> {
+ /**
+ * An object that can represent heterogeneous kinds of diagnostics,
+ * with query methods suitable for use with
+ * {@link javax.annotation.processing.Messager}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ @Invariant({
+ "getKind() != null",
+ "getMessage(null) != null",
+ "getAnnotationMirror() == null || getElement() != null",
+ "getAnnotationValue() == null || getAnnotationMirror() != null"
+ })
+ public abstract class Report {
+ /**
+ * Returns the kind of this diagnostic.
+ */
+ public abstract Kind getKind();
+
+ /**
+ * Returns a error message localized according to {@code locale},
+ * or the default locale if {@code null}.
+ */
+ public abstract String getMessage(Locale locale);
+
+ /**
+ * Returns the Java annotation processing model element
+ * associated with this diagnostic, if any.
+ */
+ public abstract Element getElement();
+
+ /**
+ * Returns the Java annotation processing model annotation mirror
+ * associated with this diagnostic, if any.
+ */
+ public abstract AnnotationMirror getAnnotationMirror();
+
+ /**
+ * Returns the Java annotation processing model annotation value
+ * associated with this diagnostic.
+ */
+ public abstract AnnotationValue getAnnotationValue();
+
+ /**
+ * Appends to {@code buffer} a string with the faulty part
+ * underlined.
+ */
+ @Requires({
+ "buffer != null",
+ "expr != null",
+ "pos >= start",
+ "pos <= end",
+ "start >= 0",
+ "start <= end",
+ "end <= expr.length()"
+ })
+ protected void underlineError(StringBuilder buffer, String expr,
+ int pos, int start, int end) {
+ int i = 0;
+ for (; i < start; ++i) {
+ buffer.append(" ");
+ }
+
+ if (pos == start) {
+ buffer.append("^");
+ } else {
+ for (; i < pos; ++i) {
+ buffer.append("~");
+ }
+ buffer.append("^");
+ ++i;
+ for (; i < end; ++i) {
+ buffer.append("~");
+ }
+ }
+ }
+ }
+
+ /**
+ * A diagnostic fired by the annotation processor or one of its
+ * components.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ public class AnnotationReport extends Report {
+ protected Kind kind;
+ protected String message;
+ protected String sourceString;
+ protected int position;
+ protected int startPosition;
+ protected int endPosition;
+ protected Element sourceElement;
+ protected AnnotationMirror annotationMirror;
+ protected AnnotationValue annotationValue;
+
+ /**
+ * Constructs a new ContractDiagnostic.
+ *
+ * @param kind the kind of this diagnostic
+ * @param message the message of this diagnostic
+ * @param sourceString the code of the contract
+ * @param position the position of the error
+ * @param startPosition the start position of the error
+ * @param endPosition the end position of the error
+ * @param sourceElement the source of this diagnostic
+ * @param annotationMirror the source annotation of this diagnostic
+ * @param annotationValue the source annotation value of this diagnostic
+ */
+ @Requires({
+ "kind != null",
+ "message != null",
+ "position >= startPosition",
+ "position <= endPosition",
+ "startPosition >= 0",
+ "startPosition <= endPosition"
+ })
+ @Ensures({
+ "kind == getKind()"
+ })
+ public AnnotationReport(Kind kind, String message, String sourceString,
+ int position, int startPosition, int endPosition,
+ Element sourceElement,
+ AnnotationMirror annotationMirror, AnnotationValue annotationValue) {
+ this.kind = kind;
+ this.message = message;
+ this.sourceString = sourceString;
+ this.position = position;
+ this.startPosition = startPosition;
+ this.endPosition = endPosition;
+ this.sourceElement = sourceElement;
+ this.annotationMirror = annotationMirror;
+ this.annotationValue = annotationValue;
+ }
+
+ /**
+ * Constructs a new ContractDiagnostic.
+ *
+ * @param kind the kind of this diagnostic
+ * @param message the message of this diagnostic
+ * @param sourceString the code of the contract
+ * @param position the position of the error
+ * @param startPosition the start position of the error
+ * @param endPosition the end position of the error
+ * @param info the source of this diagnostic
+ */
+ @Requires({
+ "kind != null",
+ "message != null",
+ "position >= startPosition",
+ "position <= endPosition",
+ "startPosition >= 0",
+ "startPosition <= endPosition"
+ })
+ @Ensures({
+ "kind == getKind()"
+ })
+ public AnnotationReport(Kind kind, String message, String sourceString,
+ int position, int startPosition, int endPosition, Object info) {
+ this(kind, message, sourceString, position, startPosition, endPosition,
+ null, null, null);
+ if (info instanceof AnnotationSourceInfo) {
+ AnnotationSourceInfo sourceInfo = (AnnotationSourceInfo) info;
+ sourceElement = sourceInfo.getElement();
+ annotationMirror = sourceInfo.getAnnotationMirror();
+ annotationValue = sourceInfo.getAnnotationValue();
+ }
+ }
+
+ @Override
+ public Kind getKind() {
+ return kind;
+ }
+
+ @Override
+ public String getMessage(Locale locale) {
+ if (sourceString == null) {
+ return message;
+ }
+ StringBuilder buffer = new StringBuilder("clause: ");
+ buffer.append(sourceString);
+ buffer.append("\n ");
+ underlineError(buffer, sourceString, position,
+ startPosition, endPosition);
+ return message + "\n" + buffer.toString();
+ }
+
+ @Override
+ public Element getElement() {
+ return sourceElement;
+ }
+
+ @Override
+ public AnnotationMirror getAnnotationMirror() {
+ return annotationMirror;
+ }
+
+ @Override
+ public AnnotationValue getAnnotationValue() {
+ return annotationValue;
+ }
+ }
+
+ /**
+ * A diagnostic issued by an underlying compiler invocation.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ @Invariant("diagnostic != null")
+ public class CompilerReport extends Report {
+ protected Diagnostic<? extends JavaFileObject> diagnostic;
+
+ /**
+ * Constructs a new CompilerReport based on
+ * {@code diagnostic}.
+ */
+ @Requires("diagnostic != null")
+ public CompilerReport(
+ Diagnostic<? extends JavaFileObject> diagnostic) {
+ this.diagnostic = diagnostic;
+ }
+
+ /**
+ * Formats a pretty-printed snippet around where the error
+ * occurred. The returned snippet has the faulty sequence
+ * underlined, if possible.
+ */
+ @Requires({
+ "sourceContent != null",
+ "sourceInfo != null"
+ })
+ protected String formatErrorSnippet(CharSequence sourceContent,
+ AnnotationSourceInfo sourceInfo) {
+ List<String> code = sourceInfo.getCode();
+ int column = (int) diagnostic.getColumnNumber();
+
+ /*
+ * Determine length of extra context to ensure we match the code
+ * expressions entirely.
+ */
+ int maxCodeLength = 0;
+ for (String expr : code) {
+ int length = expr.length();
+ if (length > maxCodeLength) {
+ maxCodeLength = length;
+ }
+ }
+
+ /* Fetch context. */
+ int errorPos = (int) diagnostic.getPosition();
+ int lineStart = errorPos - column + 1;
+ int errorStart = (int) diagnostic.getStartPosition();
+ int errorEnd = (int) diagnostic.getEndPosition();
+ String partialLine = sourceContent
+ .subSequence(lineStart, errorEnd)
+ .toString();
+
+ /* Match failed expression. */
+ String snippet = null;
+ int snippetErrorPos = -1;
+ int snippetErrorStart = -1;
+ int snippetErrorEnd = -1;
+ for (String expr : code) {
+ /* Find debug marker. */
+ String commentMarker =
+ JavaUtils.BEGIN_LOCATION_COMMENT
+ + JavaUtils.quoteComment(expr)
+ + JavaUtils.END_LOCATION_COMMENT;
+ int pos = partialLine.lastIndexOf(commentMarker);
+ if (pos != -1) {
+ snippet = expr;
+ pos += commentMarker.length();
+
+ /* Compute generated code offsets. */
+ int base = lineStart + pos;
+ snippetErrorPos = errorPos - base;
+ snippetErrorStart = errorStart - base;
+ snippetErrorEnd = errorEnd - base;
+
+ snippetErrorPos -= JavaUtils.generatedCodeLength(
+ partialLine.substring(pos, pos + snippetErrorPos));
+ snippetErrorStart -= JavaUtils.generatedCodeLength(
+ partialLine.substring(pos, pos + snippetErrorStart));
+ snippetErrorEnd -= JavaUtils.generatedCodeLength(
+ partialLine.substring(pos, pos + snippetErrorEnd));
+ }
+ }
+
+ if (snippet != null) {
+ StringBuilder buffer = new StringBuilder("clause: ");
+ buffer.append(snippet);
+ if (snippetErrorPos != -1) {
+ buffer.append("\n ");
+ int end = snippet.length();
+ if (snippetErrorPos > end) {
+ snippetErrorPos = end;
+ }
+ if (snippetErrorStart > end) {
+ snippetErrorStart = end;
+ }
+ if (snippetErrorEnd > end) {
+ snippetErrorEnd = end;
+ }
+ underlineError(buffer, snippet, snippetErrorPos,
+ snippetErrorStart, snippetErrorEnd);
+ snippet = buffer.toString();
+ }
+ }
+ return snippet;
+ }
+
+ /**
+ * Returns the {@link AnnotationSourceInfo} object associated with
+ * the underlying {@link Diagnostic}, if any.
+ */
+ protected AnnotationSourceInfo getSourceInfo() {
+ JavaFileObject source = diagnostic.getSource();
+ if (!(source instanceof SyntheticJavaFile)) {
+ return null;
+ }
+ SyntheticJavaFile synthSource = (SyntheticJavaFile) source;
+ Object info = synthSource.getSourceInfo(diagnostic.getLineNumber());
+ if (!(info instanceof AnnotationSourceInfo)) {
+ return null;
+ }
+ return (AnnotationSourceInfo) info;
+ }
+
+ @Override
+ public Kind getKind() {
+ return diagnostic.getKind();
+ }
+
+ @Override
+ public String getMessage(Locale locale) {
+ AnnotationSourceInfo sourceInfo = getSourceInfo();
+ String msg = "error in contract: ";
+
+ /*
+ * This translates confusing "'<token>' expected" messages from
+ * javac to plain "syntax error" messages.
+ *
+ * TODO(lenh): Think of a more generic way to handle
+ * compiler-specific error message rewriting.
+ */
+ String errorCode = diagnostic.getCode();
+ if (errorCode != null
+ && errorCode.startsWith("compiler.err.expected")) {
+ msg += "syntax error";
+ } else {
+ msg += diagnostic.getMessage(locale);
+ }
+
+ JavaFileObject source = diagnostic.getSource();
+ if (source != null && sourceInfo != null) {
+ try {
+ CharSequence chars = source.getCharContent(true);
+ return msg + "\n" + formatErrorSnippet(chars, sourceInfo);
+ } catch (IOException e) {
+ /* No source code available. */
+ }
+ }
+
+ return msg;
+ }
+
+ @Override
+ public Element getElement() {
+ AnnotationSourceInfo sourceInfo = getSourceInfo();
+ return getSourceInfo() == null ? null : sourceInfo.getElement();
+ }
+
+ @Override
+ public AnnotationMirror getAnnotationMirror() {
+ AnnotationSourceInfo sourceInfo = getSourceInfo();
+ return getSourceInfo() == null
+ ? null : sourceInfo.getAnnotationMirror();
+ }
+
+ @Override
+ public AnnotationValue getAnnotationValue() {
+ AnnotationSourceInfo sourceInfo = getSourceInfo();
+ return getSourceInfo() == null
+ ? null : sourceInfo.getAnnotationValue();
+ }
+ }
+
+ protected List<Report> reports;
+ protected int errorCount;
+
+ public DiagnosticManager() {
+ reports = new ArrayList<Report>();
+ errorCount = 0;
+ }
+
+ public boolean hasErrors() {
+ return errorCount != 0;
+ }
+
+ @Ensures("result >= 0")
+ public int getErrorCount() {
+ return errorCount;
+ }
+
+ @Ensures("result >= 0")
+ public int getCount() {
+ return reports.size();
+ }
+
+ @Override
+ public Iterator<Report> iterator() {
+ return reports.iterator();
+ }
+
+ /**
+ * Adds {@code r} to the list of reports of this manager.
+ */
+ @Requires("r != null")
+ public void report(Report r) {
+ if (r.getKind() == Kind.ERROR) {
+ ++errorCount;
+ }
+ reports.add(r);
+ }
+
+ /**
+ * Reports a compiler diagnostic. This diagnostic manager only
+ * reports errors from the underlying compiler tool invocation,
+ * which calls this method; anything other than an error is
+ * ignored. This prevents the annotation processor from picking up
+ * irrelevant warnings pertaining to generated code.
+ */
+ @Override
+ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+ if (diagnostic.getKind() != Kind.ERROR)
+ return;
+ report(new CompilerReport(diagnostic));
+ }
+
+ /**
+ * Reports an error.
+ */
+ public void error(String message, String sourceString,
+ int position, int startPosition, int endPosition,
+ Object info) {
+ report(new AnnotationReport(Kind.ERROR, message, sourceString,
+ position, startPosition, endPosition, info));
+ }
+
+ public void error(String message, String sourceString,
+ int position, int startPosition, int endPosition,
+ Element sourceElement, AnnotationMirror annotationMirror,
+ AnnotationValue annotationValue) {
+ report(new AnnotationReport(Kind.ERROR, message, sourceString,
+ position, startPosition, endPosition,
+ sourceElement, annotationMirror,
+ annotationValue));
+ }
+
+ /**
+ * Reports a warning.
+ */
+ public void warning(String message, String sourceString,
+ int position, int startPosition, int endPosition,
+ Object info) {
+ report(new AnnotationReport(Kind.WARNING, message, sourceString,
+ position, startPosition, endPosition, info));
+ }
+
+ public void warning(String message, String sourceString,
+ int position, int startPosition, int endPosition,
+ Element sourceElement, AnnotationMirror annotationMirror,
+ AnnotationValue annotationValue) {
+ report(new AnnotationReport(Kind.WARNING, message, sourceString,
+ position, startPosition, endPosition,
+ sourceElement, annotationMirror,
+ annotationValue));
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/FactoryUtils.java b/src/com/google/java/contract/core/apt/FactoryUtils.java
new file mode 100644
index 0000000..aa1c4d3
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/FactoryUtils.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.QualifiedElementModel;
+import com.google.java.contract.core.model.TypeName;
+
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * Utility methods for dealing with {@link javax.lang.model} and their
+ * {@link com.google.java.contract.core.apt} counterparts.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "processingEnv != null",
+ "elementUtils != null",
+ "typeUtils != null"
+})
+class FactoryUtils {
+ ProcessingEnvironment processingEnv;
+ Elements elementUtils;
+ Types typeUtils;
+
+ @Requires("processingEnv != null")
+ @Ensures({
+ "this.processingEnv == processingEnv",
+ "elementUtils == processingEnv.getElementUtils()",
+ "typeUtils == processingEnv.getTypeUtils()"
+ })
+ FactoryUtils(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ elementUtils = processingEnv.getElementUtils();
+ typeUtils = processingEnv.getTypeUtils();
+ }
+
+ void copyModifiers(Element e, QualifiedElementModel model) {
+ for (ElementModifier modifier :
+ ElementModifier.forModifiers(e.getModifiers())) {
+ model.addModifier(modifier);
+ }
+ }
+
+ /**
+ * Creates a {@link ClassName} from a {@link TypeMirror}. The
+ * created ClassName bears generic parameters, if any.
+ */
+ @Requires({
+ "type != null",
+ "type.getKind() == javax.lang.model.type.TypeKind.DECLARED"
+ })
+ @Ensures("result == null || result.getDeclaredName().equals(type.toString())")
+ ClassName getClassNameForType(TypeMirror type) {
+ DeclaredType tmp = (DeclaredType) type;
+ TypeElement element = (TypeElement) tmp.asElement();
+ String binaryName = elementUtils.getBinaryName(element)
+ .toString().replace('.', '/');
+ return new ClassName(binaryName, type.toString());
+ }
+
+ /**
+ * Creates a {@link TypeName} from a {@link TypeMirror}.
+ */
+ @Requires("type != null")
+ @Ensures("result == null || result.getDeclaredName().equals(type.toString())")
+ TypeName getTypeNameForType(TypeMirror type) {
+ switch (type.getKind()) {
+ case NONE:
+ return null;
+ default:
+ return new TypeName(type.toString());
+ }
+ }
+
+ /**
+ * Returns a Java-printable generic type name from the specified
+ * TypeParameterElement.
+ */
+ @Requires("element != null")
+ @Ensures("result != null")
+ TypeName getGenericTypeName(TypeParameterElement element) {
+ String name = element.getSimpleName().toString();
+ List<? extends TypeMirror> bounds = element.getBounds();
+ if (bounds.isEmpty()
+ || (bounds.size() == 1
+ && bounds.get(0).toString().equals("java.lang.Object"))) {
+ return new TypeName(name);
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(name);
+ buffer.append(" extends ");
+
+ Iterator<? extends TypeMirror> iter = bounds.iterator();
+ for (;;) {
+ buffer.append(iter.next().toString());
+ if (!iter.hasNext()) {
+ break;
+ }
+ buffer.append(" & ");
+ }
+
+ return new TypeName(buffer.toString());
+ }
+
+ /**
+ * Gets the contract kind of an annotation given its qualified name.
+ * Returns null if the annotation is not a contract annotation.
+ *
+ * @param annotationName the fully qualified name of the annotation
+ * @return the contract type, null if not contracts
+ */
+ ElementKind getAnnotationKindForName(AnnotationMirror annotation) {
+ String annotationName = annotation.getAnnotationType().toString();
+ ElementKind kind;
+ if (annotationName.equals("com.google.java.contract.Invariant")) {
+ kind = ElementKind.INVARIANT;
+ } else if (annotationName.equals("com.google.java.contract.Requires")) {
+ kind = ElementKind.REQUIRES;
+ } else if (annotationName.equals("com.google.java.contract.Ensures")) {
+ kind = ElementKind.ENSURES;
+ } else if (annotationName.equals("com.google.java.contract.ThrowEnsures")) {
+ kind = ElementKind.THROW_ENSURES;
+ } else {
+ kind = null;
+ }
+
+ return kind;
+ }
+
+ boolean isContractAnnotation(AnnotationMirror annotation) {
+ return getAnnotationKindForName(annotation) != null;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/JavacUtils.java b/src/com/google/java/contract/core/apt/JavacUtils.java
new file mode 100644
index 0000000..6157c8b
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/JavacUtils.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.NewArrayTree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+
+/**
+ * A com.sun.source-based utility class that extracts source
+ * information.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+class JavacUtils {
+ /**
+ * Returns the line numbers associated with {@code annotation}.
+ */
+ @Requires({
+ "processingEnv != null",
+ "element != null",
+ "annotation != null"
+ })
+ @Ensures("result != null")
+ public static List<Long> getLineNumbers(ProcessingEnvironment processingEnv,
+ Element element, AnnotationMirror annotation) {
+ Trees treeUtils = Trees.instance(processingEnv);
+ if (treeUtils == null) {
+ return Collections.emptyList();
+ }
+
+ TreePath path = treeUtils.getPath(element, annotation);
+ if (path == null) {
+ return Collections.emptyList();
+ }
+
+ CompilationUnitTree unitTree = path.getCompilationUnit();
+ LineMap lineMap = unitTree.getLineMap();
+ SourcePositions positions = treeUtils.getSourcePositions();
+
+ AnnotationTree annotationTree = (AnnotationTree) path.getLeaf();
+ AssignmentTree assignTree =
+ (AssignmentTree) annotationTree.getArguments().get(0);
+ ExpressionTree exprTree = assignTree.getExpression();
+
+ ArrayList<Long> lines = new ArrayList<Long>();
+ if (exprTree.getKind() == Kind.STRING_LITERAL) {
+ long pos = positions.getStartPosition(unitTree, exprTree);
+ lines.add(lineMap.getLineNumber(pos));
+ } else {
+ NewArrayTree valuesTree = (NewArrayTree) exprTree;
+ for (ExpressionTree valueTree : valuesTree.getInitializers()) {
+ long pos = positions.getStartPosition(unitTree, valueTree);
+ lines.add(lineMap.getLineNumber(pos));
+ }
+ }
+
+ return lines;
+ }
+
+ /**
+ * Returns the import statements in effect in the containing
+ * compilation unit of {@code element}.
+ */
+ @Requires({
+ "processingEnv != null",
+ "element != null"
+ })
+ @Ensures("result != null")
+ public static Set<String> getImportNames(ProcessingEnvironment processingEnv,
+ Element element) {
+ Trees treeUtils = Trees.instance(processingEnv);
+ if (treeUtils == null) {
+ return Collections.emptySet();
+ }
+
+ TreePath path = treeUtils.getPath(element);
+ if (path == null) {
+ return Collections.emptySet();
+ }
+
+ CompilationUnitTree unitTree = path.getCompilationUnit();
+
+ HashSet<String> importNames = new HashSet<String>();
+ for (ImportTree importTree : unitTree.getImports()) {
+ StringBuilder buffer = new StringBuilder();
+ if (importTree.isStatic()) {
+ buffer.append("static ");
+ }
+ /* TODO(lenh): Roll our own toString()? */
+ buffer.append(importTree.getQualifiedIdentifier().toString());
+ importNames.add(buffer.toString());
+ }
+
+ return importNames;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/MethodContractCreator.java b/src/com/google/java/contract/core/apt/MethodContractCreator.java
new file mode 100644
index 0000000..ff7d249
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/MethodContractCreator.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import static com.google.java.contract.core.apt.ContractCreation.createContractMethods;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ContractKind;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.TypeName;
+import com.google.java.contract.core.model.VariableModel;
+import com.google.java.contract.core.util.ElementScanner;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Element visitor responsible for decorating a {@link TypeModel}
+ * object with method-wide contract code elements.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "diagnosticManager != null",
+ "transformer != null"
+})
+public class MethodContractCreator extends ElementScanner {
+ /**
+ * Creation trait for preconditions.
+ */
+ protected class PreMethodCreationTrait
+ extends ContractExpressionCreationTrait {
+ @Requires("transformer != null")
+ public PreMethodCreationTrait(
+ ContractExpressionTransformer transformer) {
+ super(transformer);
+ }
+
+ @Override
+ public boolean transform(List<String> code, List<Long> lineNumbers,
+ Object sourceInfo) {
+ transformer.setAcceptOld(false);
+ return super.transform(code, lineNumbers, sourceInfo);
+ }
+
+ @Override
+ public String getExceptionName() {
+ return "com.google.java.contract.PreconditionError";
+ }
+ }
+
+ /**
+ * Creation trait for common transformations shared by
+ * postconditions and exceptional postconditions.
+ */
+ protected class CommonPostMethodCreationTrait
+ extends ContractExpressionCreationTrait {
+ @Requires("transformer != null")
+ public CommonPostMethodCreationTrait(
+ ContractExpressionTransformer transformer) {
+ super(transformer);
+ }
+
+ @Override
+ public boolean transform(List<String> code, List<Long> lineNumbers,
+ Object sourceInfo) {
+ int id = transformer.getNextOldId();
+ transformer.setAcceptOld(true);
+ boolean success = super.transform(code, lineNumbers, sourceInfo);
+
+ if (success) {
+ ContractKind oldKind =
+ ContractCreation.getContractKind(annotation).getOldKind();
+ Iterator<String> iterCode =
+ transformer.getOldParametersCode().iterator();
+ Iterator<Long> iterLineNumber =
+ transformer.getOldParametersLineNumbers().iterator();
+ int pos = 0;
+ while (iterCode.hasNext()) {
+ createOldMethods(oldKind, pos++, id++, iterCode.next(), annotation,
+ iterLineNumber.next());
+ }
+ }
+ return success;
+ }
+
+ @Override
+ public List<? extends VariableModel> getExtraParameters() {
+ return transformer.getOldParameters();
+ }
+
+ @Override
+ public String getExceptionName() {
+ return "com.google.java.contract.PostconditionError";
+ }
+ }
+
+ /**
+ * Creation trait for postconditions.
+ */
+ protected class PostMethodCreationTrait
+ extends CommonPostMethodCreationTrait {
+ @Requires("transformer != null")
+ public PostMethodCreationTrait(
+ ContractExpressionTransformer transformer) {
+ super(transformer);
+ }
+
+ @Override
+ public List<? extends VariableModel> getInitialParameters() {
+ if (method.isConstructor()
+ || method.getReturnType().getDeclaredName().equals("void")) {
+ return Collections.emptyList();
+ } else {
+ return Collections.singletonList(
+ getResultVariable(method.getReturnType()));
+ }
+ }
+
+ @Override
+ public List<? extends VariableModel> getInitialMockParameters() {
+ if (method.isConstructor()
+ || method.getReturnType().getDeclaredName().equals("void")) {
+ return Collections.emptyList();
+ } else {
+ return Collections.singletonList(
+ getResultVariable(annotation.getReturnType()));
+ }
+ }
+ }
+
+ /**
+ * Creation trait for exceptional postconditions.
+ */
+ protected class PostSignalMethodCreationTrait
+ extends CommonPostMethodCreationTrait {
+ protected List<String> messages;
+ protected List<String> sourceCode;
+
+ @Requires("transformer != null")
+ public PostSignalMethodCreationTrait(
+ ContractExpressionTransformer transformer) {
+ super(transformer);
+ }
+
+ @Override
+ public boolean visit(ContractAnnotationModel annotation) {
+ this.annotation = annotation;
+
+ List<String> assocs = annotation.getValues();
+ int n = assocs.size() / 2;
+
+ ArrayList<String> code = new ArrayList<String>(n);
+ ArrayList<String> msg = new ArrayList<String>(n);
+ ArrayList<String> src = new ArrayList<String>(n);
+ ArrayList<Long> lines = new ArrayList<Long>(n);
+
+ Iterator<String> it = assocs.iterator();
+ Iterator<Long> itLineNumber = annotation.getLineNumbers().iterator();
+ try {
+ while (it.hasNext()) {
+ String exceptionType = it.next();
+ String postcondition = it.next();
+ code.add("!(signal instanceof " + exceptionType + ") || "
+ + postcondition);
+ msg.add(exceptionType + " => " + postcondition);
+ src.add(postcondition);
+
+ /*
+ * Throw away the line number information of the exception
+ * type.
+ */
+ itLineNumber.next();
+ lines.add(itLineNumber.next());
+ }
+ } catch (NoSuchElementException e) {
+ diagnosticManager.warning(
+ "extra exception type in "
+ + "'com.google.java.contract.ThrowEnsures'; "
+ + "ignored",
+ assocs.get(assocs.size() - 1), 0, 0, 0,
+ annotation.getSourceInfo());
+ }
+
+ if (!transform(code, lines, annotation.getSourceInfo())) {
+ return false;
+ }
+
+ messages = msg;
+ sourceCode = src;
+ return true;
+ }
+
+ @Override
+ public List<? extends VariableModel> getInitialParameters() {
+ return Collections.singletonList(getSignalVariable());
+ }
+
+ @Override
+ public List<String> getMessages() {
+ return messages;
+ }
+
+ @Override
+ public List<String> getSourceExpressions() {
+ return sourceCode;
+ }
+ }
+
+ protected DiagnosticManager diagnosticManager;
+
+ protected MethodModel method;
+ protected ContractMethodModel preMethod;
+ protected ContractMethodModel postMethod;
+ protected ContractMethodModel postSignalMethod;
+
+ protected ContractExpressionTransformer transformer;
+
+ /**
+ * Constructs a new MethodContractCreator.
+ */
+ @Requires("diagnosticManager != null")
+ public MethodContractCreator(DiagnosticManager diagnosticManager) {
+ this.diagnosticManager = diagnosticManager;
+ method = null;
+ preMethod = null;
+ postMethod = null;
+ postSignalMethod = null;
+ transformer = new ContractExpressionTransformer(diagnosticManager, true);
+ }
+
+ @Override
+ public void visitMethod(MethodModel method) {
+ if (this.method != null) {
+ throw new IllegalStateException();
+ }
+ this.method = method;
+ super.visitMethod(method);
+ }
+
+ @Override
+ public void visitContractAnnotation(ContractAnnotationModel annotation) {
+ List<String> code = annotation.getValues();
+
+ if (annotation.getKind().equals(ElementKind.REQUIRES)) {
+ PreMethodCreationTrait trait = new PreMethodCreationTrait(transformer);
+ preMethod = createContractMethods(trait, preMethod, annotation);
+ } else if (annotation.getKind().equals(ElementKind.ENSURES)) {
+ PostMethodCreationTrait trait = new PostMethodCreationTrait(transformer);
+ postMethod = createContractMethods(trait, postMethod, annotation);
+ } else if (annotation.getKind().equals(ElementKind.THROW_ENSURES)) {
+ PostSignalMethodCreationTrait trait =
+ new PostSignalMethodCreationTrait(transformer);
+ postSignalMethod = createContractMethods(trait, postSignalMethod,
+ annotation);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Requires("type != null")
+ @Ensures("result != null")
+ private static VariableModel getResultVariable(TypeName type) {
+ VariableModel var =
+ new VariableModel(ElementKind.PARAMETER, JavaUtils.RESULT_VARIABLE,
+ type);
+ var.addModifier(ElementModifier.FINAL);
+ return var;
+ }
+
+ @Ensures("result != null")
+ private static VariableModel getSignalVariable() {
+ VariableModel var =
+ new VariableModel(ElementKind.PARAMETER, JavaUtils.SIGNAL_VARIABLE,
+ new ClassName("java/lang/Exception"));
+ var.addModifier(ElementModifier.FINAL);
+ return var;
+ }
+
+ /**
+ * Creates contract and helper methods according to the parameters,
+ * and adds it to the parent type.
+ *
+ * @param kind the kind of contract method to create
+ * @param pos the relative position of {@code expr} in its
+ * annotation
+ * @param id the contract method ID
+ * @param expr the expression computing the old value
+ * @param annotation the annotation value from which this contract
+ * is created
+ */
+ @Requires({
+ "kind != null",
+ "pos >= 0",
+ "id >= 0",
+ "pos <= id",
+ "expr != null",
+ "annotation != null",
+ "kind.isOld()",
+ "lineNumber == null || lineNumber >= 1"
+ })
+ private void createOldMethods(ContractKind kind,
+ int pos, int id, String expr, ContractAnnotationModel annotation,
+ Long lineNumber) {
+ MethodModel helper =
+ ContractCreation.createBlankContractHelper(kind, annotation,
+ "$" + Integer.toString(pos));
+ helper.setReturnType(new ClassName("java/lang/Object"));
+
+ if (helper.getKind() == ElementKind.CONTRACT_METHOD) {
+ ContractMethodModel helperContract = (ContractMethodModel) helper;
+ if (lineNumber != null) {
+ helperContract.setLineNumbers(Collections.singletonList(lineNumber));
+ }
+ String code = expr;
+ if (!annotation.isVirtual()) {
+ code = ContractCreation
+ .rebaseLocalCalls(expr, JavaUtils.THAT_VARIABLE, null);
+ }
+ helperContract.addStatement("return " + code + ";");
+ }
+
+ ContractMethodModel contract =
+ ContractCreation.createBlankContractMethod(kind, annotation, "$" + id);
+ contract.setReturnType(new ClassName("java/lang/Object"));
+ contract.setId(id);
+
+ contract.addStatement("return "
+ + ContractCreation.getHelperCallCode(helper, annotation) + ";");
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/SimpleContractCreationTrait.java b/src/com/google/java/contract/core/apt/SimpleContractCreationTrait.java
new file mode 100644
index 0000000..6425b68
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/SimpleContractCreationTrait.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.VariableModel;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A simple contract creation trait that returns the annotation values
+ * unmodified.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+class SimpleContractCreationTrait implements ContractCreationTrait {
+ protected ContractAnnotationModel annotation;
+
+ @Override
+ public boolean visit(ContractAnnotationModel annotation) {
+ this.annotation = annotation;
+ return true;
+ }
+
+ @Override
+ public List<String> getExpressions() {
+ return annotation.getValues();
+ }
+
+ @Override
+ public List<String> getMessages() {
+ return annotation.getValues();
+ }
+
+ @Override
+ public List<String> getSourceExpressions() {
+ return annotation.getValues();
+ }
+
+ @Override
+ public List<? extends VariableModel> getInitialParameters() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<? extends VariableModel> getExtraParameters() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<? extends VariableModel> getInitialMockParameters() {
+ return getInitialParameters();
+ }
+
+ @Override
+ public List<? extends VariableModel> getExtraMockParameters() {
+ return getExtraParameters();
+ }
+
+ @Override
+ public String getExceptionName() {
+ return "java.lang.AssertionError";
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/SourceDependencyParser.java b/src/com/google/java/contract/core/apt/SourceDependencyParser.java
new file mode 100644
index 0000000..be868b8
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/SourceDependencyParser.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.util.BalancedTokenizer;
+import com.google.java.contract.core.util.JavaTokenizer;
+import com.google.java.contract.core.util.JavaUtils;
+import com.google.java.contract.core.util.JavaUtils.ParseException;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A flexible parser that extracts import statements and line number
+ * information from a Java source file.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant({
+ "!canQueryResults() || getImportNames() != null",
+ "!canQueryResults() || !getImportNames().contains(null)",
+ "!canQueryResults() || getContractLineNumbers() != null",
+ "!canQueryResults() || !getContractLineNumbers().entrySet().contains(null)",
+ "!canQueryResults() || " +
+ "Iterables.all(getContractLineNumbers().values(), " +
+ " Predicates.<Long>all(Predicates.between(1L, null)))",
+ "source != null"
+})
+public class SourceDependencyParser {
+ /**
+ * The source file being parsed.
+ */
+ protected Reader source;
+
+ /**
+ * A set of import targets that are in effect in the source file.
+ */
+ protected Set<String> importNames;
+
+ /**
+ * The successive positions of contracts in the source file, in each
+ * top-level class. Each clause has an entry in the list. Each entry
+ * for a top-level class includes contracts in nested classes.
+ */
+ protected Map<ClassName, List<Long>> contractLineNumbers;
+
+ /**
+ * Whether {@code source} has been parsed yet.
+ */
+ protected boolean parsed;
+
+ private static final List<String> TYPE_KEYWORDS =
+ Arrays.asList("class", "enum", "interface");
+ private static final List<String> CONTRACT_TYPES =
+ Arrays.asList("Requires", "Ensures", "ThrowEnsures", "Invariant");
+
+ /**
+ * Constructs a new SourceDependencyParser.
+ *
+ * @param source the source file
+ */
+ public SourceDependencyParser(Reader source) {
+ this.source = source;
+ importNames = new HashSet<String>();
+ contractLineNumbers = new HashMap<ClassName, List<Long>>();
+ parsed = false;
+ }
+
+ /**
+ * Parses the source file.
+ *
+ * @throws ParseException if a parsing error occurs
+ */
+ @Ensures("canQueryResults()")
+ public void parse() throws ParseException {
+ if (parsed) {
+ return;
+ }
+
+ try {
+ BalancedTokenizer tokenizer = new BalancedTokenizer(source);
+ String packageName = null;
+ ClassName className = null;
+ ArrayList<Long> orphanLineNumbers = new ArrayList<Long>();
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.next();
+ switch (token.kind) {
+ case WORD:
+ if (tokenizer.getCurrentLevel() == 0) {
+ if (token.text.equals("package")) {
+ packageName = JavaUtils.parseQualifiedName(tokenizer);
+ } else if (token.text.equals("import")) {
+ String name = JavaUtils.parseQualifiedName(tokenizer, true);
+ if (name.equals("static")) {
+ name += " " + JavaUtils.parseQualifiedName(tokenizer, true);
+ }
+ importNames.add(name);
+ } else if (TYPE_KEYWORDS.contains(token.text)) {
+ String name = JavaUtils.parseQualifiedName(tokenizer);
+ if (packageName != null) {
+ name = packageName + "." + name;
+ }
+ className = new ClassName(name.replace('.', '/'));
+ contractLineNumbers
+ .put(className, new ArrayList<Long>(orphanLineNumbers));
+ orphanLineNumbers.clear();
+ JavaUtils.skipPast(tokenizer, "{");
+ }
+ }
+ break;
+
+ case SYMBOL:
+ if (tokenizer.getCurrentLevel() == 0 && token.text.equals("}")) {
+ className = null;
+ } else {
+ if (token.text.equals("@")) {
+ String annotationType = JavaUtils.parseQualifiedName(tokenizer);
+ if (annotationType.startsWith("com.google.java.contract.")
+ || (CONTRACT_TYPES.contains(annotationType)
+ && ((packageName != null
+ && packageName.equals("com.google.java.contract"))
+ || importNames.contains("com.google.java.contract."
+ + annotationType)
+ || importNames.contains("com.google.java.contract.*")))) {
+ List<Long> lineNumbers;
+ if (className != null) {
+ lineNumbers = contractLineNumbers.get(className);
+ } else {
+ lineNumbers = orphanLineNumbers;
+ }
+ parseContractClauses(tokenizer, lineNumbers);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ parsed = true;
+ } catch (NoSuchElementException e) {
+ throw new ParseException(e);
+ }
+ }
+
+ @Requires({
+ "tokenizer != null",
+ "lineNumbers != null"
+ })
+ private void parseContractClauses(BalancedTokenizer tokenizer,
+ List<Long> lineNumbers) {
+ boolean expectClause = true;
+ while (tokenizer.hasNext()) {
+ long lineNumber = tokenizer.getCurrentLineNumber();
+ JavaTokenizer.Token token = tokenizer.next();
+ if (token.text.equals(")")) {
+ return;
+ } else if (expectClause && token.kind == JavaTokenizer.TokenKind.QUOTE) {
+ lineNumbers.add(lineNumber);
+ expectClause = false;
+ } else if (token.text.equals(",")) {
+ expectClause = true;
+ }
+ }
+ }
+
+ public boolean canQueryResults() {
+ return parsed;
+ }
+
+ @Requires("canQueryResults()")
+ public Set<String> getImportNames() {
+ return Collections.unmodifiableSet(importNames);
+ }
+
+ @Requires("canQueryResults()")
+ public Map<ClassName, List<Long>> getContractLineNumbers() {
+ return Collections.unmodifiableMap(contractLineNumbers);
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/SourcePreprocessor.java b/src/com/google/java/contract/core/apt/SourcePreprocessor.java
new file mode 100644
index 0000000..80e7021
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/SourcePreprocessor.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.util.JavaUtils;
+import com.google.java.contract.core.util.JavaUtils.ParseException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.ObjectOutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * Main class of Contracts for Java source dependency preprocessor.
+ *
+ * <p>The program takes a list of file names as input and produces
+ * matching source dependency files as output. If the system property
+ * {@code com.google.java.contract.depsoutput} is defined, then dependency files
+ * are output in that directory, following the natural hierarchy of
+ * the Java classes.
+ *
+ * <p>If a source file name does not end with the {@code .java}
+ * extension, that extension is appended to the file name.
+ *
+ * <p>Source dependency files contain information about import
+ * statements in effect in the corresponding source file as well as
+ * line numbering information for contracts. They can be provided to
+ * the annotation processor through the {@code com.google.java.contract.depspath}
+ * option.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class SourcePreprocessor {
+ public static void main(String[] args)
+ throws IOException, ParseException {
+ String depout = System.getProperty("com.google.java.contract.depsoutput");
+ for (String arg : args) {
+ if (arg.startsWith("-")) {
+ continue;
+ }
+
+ String baseName = arg;
+ if (arg.endsWith(Kind.SOURCE.extension)) {
+ baseName = baseName
+ .substring(0, baseName.length() - Kind.SOURCE.extension.length());
+ }
+
+ File fileName = new File(baseName + Kind.SOURCE.extension);
+ String dir = fileName.getParent();
+ dir = dir == null ? "" : dir + "/";
+
+ FileInputStream in = new FileInputStream(arg);
+
+ SourceDependencyParser parser =
+ new SourceDependencyParser(new InputStreamReader(in));
+ try {
+ parser.parse();
+ } catch (ParseException e) {
+ throw new ParseException(
+ fileName + " is malformed; "
+ + "you should not compile contracts before compiling "
+ + "the actual source files; "
+ + "if this file is valid Java, you found a bug in Contracts for Java; "
+ + "please email 'davidmorgan at google.com'",
+ e);
+ }
+
+ Set<String> importNames = parser.getImportNames();
+ Map<ClassName, List<Long>> contractLineNumbers =
+ parser.getContractLineNumbers();
+
+ if (contractLineNumbers.isEmpty()) {
+ continue;
+ }
+
+ for (Map.Entry<ClassName, List<Long>> entry :
+ contractLineNumbers.entrySet()) {
+ ClassName className = entry.getKey();
+ File outputFileName;
+ if (depout == null) {
+ outputFileName = new File(dir + className.getSimpleName()
+ + JavaUtils.SOURCE_DEPENDENCY_EXTENSION);
+ } else {
+ outputFileName = new File(depout + "/" + className.getBinaryName()
+ + JavaUtils.SOURCE_DEPENDENCY_EXTENSION);
+ }
+
+ outputFileName.getParentFile().mkdirs();
+ FileOutputStream out = new FileOutputStream(outputFileName);
+ ObjectOutputStream oout = new ObjectOutputStream(out);
+
+ oout.writeObject(importNames);
+ oout.writeObject(entry.getValue());
+ oout.close();
+ }
+
+ in.close();
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/SuperCallBuilder.java b/src/com/google/java/contract/core/apt/SuperCallBuilder.java
new file mode 100644
index 0000000..57ddfca
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/SuperCallBuilder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.TypeModel;
+
+import java.util.List;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.ElementScanner6;
+
+/**
+ * An element visitor that extracts constructor arguments from a
+ * callable constructor. It always picks a constructor with the
+ * broadest access level: if the original class was correct, then at
+ * least that one should be accessible.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("subtype != null")
+class SuperCallBuilder extends ElementScanner6<Void, Void> {
+ protected FactoryUtils utils;
+ protected DeclaredType typeMirror;
+ protected TypeModel subtype;
+ protected ElementModifier constructorFound;
+
+ /**
+ * Constructs a new SuperCallBuilder that builds a constructor call
+ * for {@code subtype}. The super call will be made against
+ * {@code typeMirror}, which may differ from the raw element this
+ * instance is visiting (e.g. it can be a specialized version of
+ * a generic class).
+ */
+ @Requires({
+ "subtype != null",
+ "utils != null"
+ })
+ SuperCallBuilder(DeclaredType typeMirror, TypeModel subtype,
+ FactoryUtils utils) {
+ this.utils = utils;
+ this.typeMirror = typeMirror;
+ this.subtype = subtype;
+ constructorFound = null;
+ }
+
+ @Override
+ public Void visitType(TypeElement e, Void unused) {
+ scan(ElementFilter.constructorsIn(e.getEnclosedElements()), null);
+ return null;
+ }
+
+ @Override
+ public Void visitExecutable(ExecutableElement e, Void unused) {
+ ElementModifier v = ElementModifier.visibilityIn(
+ ElementModifier.forModifiers(e.getModifiers()));
+ if (constructorFound != null
+ && constructorFound.ordinal() <= v.ordinal()) {
+ return null;
+ }
+
+ subtype.clearSuperArguments();
+ ExecutableType execType =
+ (ExecutableType) utils.typeUtils.asMemberOf(typeMirror, e);
+ List<? extends TypeMirror> paramTypes = execType.getParameterTypes();
+ for (TypeMirror t : paramTypes) {
+ subtype.addSuperArgument(utils.getTypeNameForType(t));
+ }
+
+ constructorFound = v;
+ return null;
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/TypeBuilder.java b/src/com/google/java/contract/core/apt/TypeBuilder.java
new file mode 100644
index 0000000..65cef28
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/TypeBuilder.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModel;
+import com.google.java.contract.core.model.ElementModifier;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.VariableModel;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+
+/**
+ * An element visitor that builds a {@link TypeModel} object. It
+ * recursively builds child (nested) types. This visitor takes an
+ * element parameter, to which children are added.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at Invariant({
+ "diagnosticManager != null",
+ "methodMap != null"
+})
+class TypeBuilder extends AbstractTypeBuilder {
+ protected class ContractableMethod {
+ protected ExecutableElement mirror;
+ protected MethodModel element;
+
+ public ContractableMethod(ExecutableElement mirror,
+ MethodModel element) {
+ this.mirror = mirror;
+ this.element = element;
+ }
+ }
+
+ /**
+ * An element visitor that adds inherited contract annotations to
+ * an existing TypeModel object.
+ */
+ protected class ContractExtensionBuilder extends AbstractTypeBuilder {
+ protected TypeElement mirror;
+
+ public ContractExtensionBuilder() {
+ super(TypeBuilder.this.utils, TypeBuilder.this.diagnosticManager);
+ }
+
+ @Override
+ public Void visitType(TypeElement e, ElementModel p) {
+ if (mirror != null) {
+ throw new IllegalStateException();
+ }
+
+ mirror = e;
+ scanAnnotations(e, false, utils.getClassNameForType(e.asType()), type);
+
+ scan(ElementFilter.methodsIn(e.getEnclosedElements()), type);
+
+ scanSuper(e);
+ return null;
+ }
+
+ @Override
+ public Void visitExecutable(ExecutableElement e, ElementModel p) {
+ String name = e.getSimpleName().toString();
+ ArrayList<ContractableMethod> candidates = methodMap.get(name);
+ if (candidates == null) {
+ return null;
+ }
+ for (ContractableMethod overrider : candidates) {
+ if (utils.elementUtils.overrides(overrider.mirror, e,
+ rootMirror)) {
+ scanAnnotations(e, false,
+ utils.getClassNameForType(mirror.asType()),
+ overrider.element);
+ }
+ }
+ return null;
+ }
+ }
+
+ protected ClassLoader sourceDependencyLoader;
+
+ /**
+ * The resulting top-level type.
+ */
+ protected TypeModel type;
+
+ /**
+ * The root TypeElement.
+ */
+ protected TypeElement rootMirror;
+
+ /**
+ * Import statements that apply to this type source code.
+ */
+ protected Set<String> importNames;
+
+ /**
+ * A map of resulting methods; used internally for contract
+ * inheritance propagation.
+ */
+ protected HashMap<String, ArrayList<ContractableMethod>> methodMap;
+
+ TypeBuilder(Set<String> importNames,
+ Iterator<Long> rootLineNumberIterator,
+ FactoryUtils utils,
+ ClassLoader sourceDependencyLoader,
+ DiagnosticManager diagnosticManager) {
+ super(utils, diagnosticManager);
+ this.sourceDependencyLoader = sourceDependencyLoader;
+ type = null;
+ rootMirror = null;
+ this.importNames = importNames;
+ this.rootLineNumberIterator = rootLineNumberIterator;
+ methodMap = new HashMap<String, ArrayList<ContractableMethod>>();
+ }
+
+ TypeBuilder(FactoryUtils utils,
+ ClassLoader sourceDependencyLoader,
+ DiagnosticManager diagnosticManager) {
+ this(null, null, utils, sourceDependencyLoader, diagnosticManager);
+ }
+
+ TypeModel getType() {
+ return type;
+ }
+
+ @Override
+ @Ensures("type != null")
+ public Void visitType(TypeElement e, ElementModel p) {
+ /* Inner types. */
+ if (type != null) {
+ TypeBuilder builder =
+ new TypeBuilder(importNames, rootLineNumberIterator,
+ utils, sourceDependencyLoader, diagnosticManager);
+ e.accept(builder, p);
+ p.addEnclosedElement(builder.type);
+ return null;
+ }
+
+ /* Root type. */
+ rootMirror = e;
+
+ /* Create type. */
+ ElementKind kind = null;
+ switch (e.getKind()) {
+ case INTERFACE:
+ kind = ElementKind.INTERFACE;
+ break;
+ case ENUM:
+ kind = ElementKind.ENUM;
+ break;
+ case CLASS:
+ kind = ElementKind.CLASS;
+ break;
+ case ANNOTATION_TYPE:
+ kind = ElementKind.ANNOTATION_TYPE;
+ break;
+ default:
+ return null;
+ }
+ type = new TypeModel(kind, utils.getClassNameForType(e.asType()));
+ utils.copyModifiers(e, type);
+ if (kind == ElementKind.ENUM) {
+ type.removeModifier(ElementModifier.FINAL);
+ }
+
+ /*
+ * Fetch import and global line number information, from a
+ * source dependency file, if available, falling back to
+ * com.sun.source, if not.
+ */
+ if (importNames == null) {
+ if (sourceDependencyLoader != null) {
+ try {
+ fetchSourceDependency();
+ } catch (IOException ioe) {
+ /* Consider that no information is available. */
+ }
+ }
+ if (importNames == null) {
+ importNames = getImportNames(e);
+ }
+ for (String importName : importNames) {
+ type.addImportName(importName);
+ }
+ }
+
+ /* Set superclass and interfaces. */
+ TypeMirror superclass = e.getSuperclass();
+ if (superclass.getKind() == TypeKind.DECLARED) {
+ type.setSuperclass(utils.getClassNameForType(superclass));
+ }
+ List<? extends TypeMirror> interfaces = e.getInterfaces();
+ for (TypeMirror iface : interfaces) {
+ type.addInterface(utils.getClassNameForType(iface));
+ }
+
+ /* Construct super() call. */
+ if (kind != ElementKind.ENUM) {
+ TypeMirror superMirror = e.getSuperclass();
+ if (superMirror.getKind() == TypeKind.DECLARED) {
+ TypeElement superType =
+ (TypeElement) utils.typeUtils.asElement(superMirror);
+ SuperCallBuilder visitor =
+ new SuperCallBuilder((DeclaredType) superMirror, type, utils);
+ superType.accept(visitor, null);
+ }
+ }
+
+ /* Process generic signature. */
+ List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
+ for (TypeParameterElement tp : typeParams) {
+ type.addTypeParameter(utils.getGenericTypeName(tp));
+ }
+
+ /* Process annotations. */
+ scanAnnotations(e, true, type.getName(), type);
+
+ /* Process members. */
+ scan(e.getEnclosedElements(), type);
+
+ /* Add inherited contract annotations. */
+ scanSuper(e);
+
+ return null;
+ }
+
+ @Override
+ protected void visitAnnotation(Element parent, AnnotationMirror annotation,
+ boolean primary, ClassName owner,
+ ElementModel p) {
+ if (utils.isContractAnnotation(annotation)) {
+ ContractAnnotationModel model =
+ createContractModel(parent, annotation, primary, owner);
+ if (model == null) {
+ return;
+ }
+ if (type.getKind() == ElementKind.ANNOTATION_TYPE) {
+ AnnotationSourceInfo asi = (AnnotationSourceInfo) model.getSourceInfo();
+ /* Do not add contracts to annotations. Warn the user instead. */
+ diagnosticManager.warning("Contracts can't be applied to annotations. "
+ + "The following annotation will not "
+ + "perform any contract check: " +
+ type.toString(),
+ asi.getAnnotationValue().toString(), 0, 0, 0,
+ asi);
+ } else {
+ p.addEnclosedElement(model);
+ }
+ }
+ }
+
+ /**
+ * Fetches source dependency information from the system class
+ * loader.
+ */
+ @Requires({
+ "sourceDependencyLoader != null",
+ "type != null"
+ })
+ @Ensures({
+ "importNames != null",
+ "rootLineNumberIterator != null"
+ })
+ @SuppressWarnings("unchecked")
+ protected void fetchSourceDependency() throws IOException {
+ String fileName = type.getName().getBinaryName()
+ + JavaUtils.SOURCE_DEPENDENCY_EXTENSION;
+ InputStream in =
+ sourceDependencyLoader.getResourceAsStream(fileName);
+ if (in == null) {
+ throw new FileNotFoundException();
+ }
+ ObjectInputStream oin = new ObjectInputStream(in);
+ try {
+ importNames = (Set<String>) oin.readObject();
+ rootLineNumberIterator = ((List<Long>) oin.readObject()).iterator();
+ } catch (ClassNotFoundException e) {
+ throw new IOException(e);
+ }
+ oin.close();
+ }
+
+ @Override
+ public Void visitVariable(VariableElement e, ElementModel p) {
+ ElementKind kind = null;
+ switch (e.getKind()) {
+ case ENUM_CONSTANT:
+ kind = ElementKind.CONSTANT;
+ break;
+ default:
+ if (p.getKind().isType()) {
+ kind = ElementKind.FIELD;
+ } else {
+ kind = ElementKind.PARAMETER;
+ }
+ }
+ VariableModel variable =
+ new VariableModel(kind, e.getSimpleName().toString(),
+ utils.getTypeNameForType(e.asType()));
+ utils.copyModifiers(e, variable);
+
+ scanAnnotations(e, true, type.getName(), variable);
+
+ p.addEnclosedElement(variable);
+ return null;
+ }
+
+ @Override
+ public Void visitExecutable(ExecutableElement e, ElementModel p) {
+ MethodModel exec = null;
+ String name = e.getSimpleName().toString();
+
+ /*
+ * For enum types, synthesized methods values() and
+ * valueOf(String) are reflected by the API but must not be
+ * reproduced in the mock.
+ */
+ if (p.getKind() == ElementKind.ENUM) {
+ ExecutableType t = (ExecutableType) e.asType();
+ if (name.equals("values")) {
+ if (t.getParameterTypes().isEmpty()) {
+ return null;
+ }
+ } else if (name.equals("valueOf")) {
+ List<TypeMirror> valueOfParameterTypes =
+ Collections.singletonList(
+ utils.elementUtils
+ .getTypeElement("java.lang.String").asType());
+ if (t.getParameterTypes().equals(valueOfParameterTypes)) {
+ return null;
+ }
+ }
+ }
+
+ /* Create element; decide if constructor or not. */
+ if (name.toString().equals("<init>")) {
+ exec = new MethodModel();
+ } else {
+ exec = new MethodModel(ElementKind.METHOD, name,
+ utils.getTypeNameForType(e.getReturnType()));
+ }
+ utils.copyModifiers(e, exec);
+
+ /* Add generic signature. */
+ List<? extends TypeParameterElement> genericTypes = e.getTypeParameters();
+ for (TypeParameterElement tp : genericTypes) {
+ exec.addTypeParameter(utils.getGenericTypeName(tp));
+ }
+
+ /* Add parameters. */
+ scan(e.getParameters(), exec);
+ exec.setVariadic(e.isVarArgs());
+
+ /* Add throws list. */
+ for (TypeMirror tt : e.getThrownTypes()) {
+ exec.addException(utils.getTypeNameForType(tt));
+ }
+
+ /* Add annotations. */
+ scanAnnotations(e, true, type.getName(), exec);
+
+ /* Register method. */
+ p.addEnclosedElement(exec);
+ addMethod(name, e, exec);
+
+ return null;
+ }
+
+ /**
+ * Adds an association to the method map.
+ */
+ protected void addMethod(String k, ExecutableElement e, MethodModel exec) {
+ ArrayList<ContractableMethod> list = methodMap.get(k);
+ if (list == null) {
+ list = new ArrayList<ContractableMethod>();
+ methodMap.put(k, list);
+ }
+ list.add(new ContractableMethod(e, exec));
+ }
+
+ /**
+ * Visits the superclass and interfaces of the specified
+ * TypeElement with a ContractExtensionBuilder.
+ */
+ protected void scanSuper(TypeElement e) {
+ TypeElement superElement =
+ (TypeElement) utils.typeUtils.asElement(e.getSuperclass());
+ if (superElement != null) {
+ superElement.accept(new ContractExtensionBuilder(), type);
+ }
+ for (TypeMirror iface : e.getInterfaces()) {
+ TypeElement ifaceElement =
+ (TypeElement) utils.typeUtils.asElement(iface);
+ ifaceElement.accept(new ContractExtensionBuilder(), type);
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/apt/TypeFactory.java b/src/com/google/java/contract/core/apt/TypeFactory.java
new file mode 100644
index 0000000..7bceb9c
--- /dev/null
+++ b/src/com/google/java/contract/core/apt/TypeFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.apt;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.net.URLClassLoader;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * The TypeFactory creates {@link Type} objects from
+ * {@link TypeElement} objects. {@link Type} and all nested elements
+ * reflect the structure of Java types (classes, interfaces, methods,
+ * fields, annotations, ...) in Contracts for Java. Only the needed
+ * parts are reflected; unnecessary information is discarded.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+class TypeFactory {
+ protected URLClassLoader sourceDependencyLoader;
+
+ protected FactoryUtils utils;
+
+ @Requires("utils != null")
+ TypeFactory(FactoryUtils utils,
+ String sourceDependencyPath) {
+ sourceDependencyLoader = null;
+ if (sourceDependencyPath != null) {
+ sourceDependencyLoader =
+ JavaUtils.getLoaderForPath(sourceDependencyPath);
+ }
+
+ this.utils = utils;
+ }
+
+ /**
+ * Returns a {@link TypeModel} instance representing the specified
+ * {@link TypeElement}.
+ */
+ @Requires({
+ "element != null",
+ "diagnosticManager != null"
+ })
+ @Ensures({
+ "result != null",
+ "result.getName().getQualifiedName()" +
+ ".equals(element.getQualifiedName().toString())"
+ })
+ TypeModel createType(TypeElement element,
+ DiagnosticManager diagnosticManager) {
+ String name = utils.elementUtils.getBinaryName(element)
+ .toString().replace('.', '/');
+ TypeBuilder visitor =
+ new TypeBuilder(utils, sourceDependencyLoader, diagnosticManager);
+ element.accept(visitor, null);
+ return visitor.getType();
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ClassName.java b/src/com/google/java/contract/core/model/ClassName.java
new file mode 100644
index 0000000..fb2542f
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ClassName.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.util.Predicate;
+
+/**
+ * A class name. This class supports the following formats:
+ *
+ * <ul>
+ * <li>simple: without package or nesting prefix;
+ * <li>(fully) qualified: with package prefix and enclosing classes
+ * separated with dots;
+ * <li>semi-qualified: with dot-separated package prefix (any '$' from
+ * class nesting remains);
+ * <li>declared: same as qualified, plus generic parameters;
+ * <li>binary: as found in bytecode (slash-separated package prefix).
+ * </ul>
+ *
+ * <p>For primitive and array types, use {@link TypeName}.
+ *
+ * <p>Anonymous classes do not have a proper name, and so cannot be
+ * designated by objects of this class.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "isSimpleName(getSimpleName())",
+ "isQualifiedName(getQualifiedName())",
+ "isQualifiedName(getSemiQualifiedName())",
+ "isBinaryName(getBinaryName())"
+})
+public class ClassName extends TypeName {
+ protected String simpleName;
+ protected String qualifiedName;
+ protected String semiQualifiedName;
+ protected String binaryName;
+
+ /**
+ * Constructs a new ClassName from its binary name. Other forms are
+ * inferred. The resulting class name is not generic (has no generic
+ * parameter).
+ */
+ @Requires("isBinaryName(binaryName)")
+ @Ensures("binaryName.equals(getBinaryName())")
+ public ClassName(String binaryName) {
+ this.binaryName = binaryName;
+ simpleName = null;
+ inferSemiQualifiedName();
+ inferQualifiedName();
+ inferSimpleName();
+ declaredName = qualifiedName;
+ }
+
+ /**
+ * Constructs a new ClassName from its binary and declared
+ * names. Other forms are inferred. The binary and declared names
+ * must represent the same type.
+ */
+ @Requires({
+ "isBinaryName(binaryName)",
+ "declaredName != null"
+ })
+ @Ensures({
+ "binaryName.equals(getBinaryName())",
+ "declaredName.equals(getDeclaredName())"
+ })
+ public ClassName(String binaryName, String declaredName) {
+ this.binaryName = binaryName;
+ this.declaredName = declaredName;
+ simpleName = null;
+ inferSemiQualifiedName();
+ inferQualifiedName();
+ inferSimpleName();
+ assertDeclaredQualifiedMatch();
+ }
+
+ @Requires({
+ "binaryName != null",
+ "declaredName != null",
+ "simpleName != null",
+ "binaryName.endsWith(simpleName)"
+ })
+ @Ensures({
+ "binaryName.equals(getBinaryName())",
+ "declaredName.equals(getDeclaredName())",
+ "simpleName.equals(getSimpleName())"
+ })
+ public ClassName(String binaryName, String declaredName, String simpleName) {
+ this.binaryName = binaryName;
+ this.declaredName = declaredName;
+ this.simpleName = simpleName;
+ inferSemiQualifiedName();
+ inferQualifiedName();
+ assertDeclaredQualifiedMatch();
+ }
+
+ protected void assertDeclaredQualifiedMatch() {
+ if (!declaredName.replaceAll("<[^.]*>", "").startsWith(qualifiedName)) {
+ throw new IllegalArgumentException(
+ "declared name '" + declaredName
+ + "' does not match qualified name '" + qualifiedName + "'");
+ }
+ }
+
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ /**
+ * Returns the relative component of {@code pathName}.
+ *
+ * @param pathName a dot-separated path
+ */
+ @Requires("pathName != null")
+ @Ensures("result != null")
+ public static String getRelativeName(String pathName) {
+ int lastSep = pathName.lastIndexOf('.');
+ if (lastSep == -1) {
+ return pathName;
+ } else {
+ return pathName.substring(lastSep + 1);
+ }
+ }
+
+ /**
+ * Returns the package component of {@code pathName}.
+ *
+ * @param pathName a dot-separated path
+ */
+ @Requires("pathName != null")
+ @Ensures("result != null")
+ public static String getPackageName(String pathName) {
+ int lastSep = pathName.lastIndexOf('.');
+ if (lastSep == -1) {
+ return "";
+ } else {
+ return pathName.substring(0, lastSep);
+ }
+ }
+
+ public String getQualifiedName() {
+ return qualifiedName;
+ }
+
+ public String getSemiQualifiedName() {
+ return semiQualifiedName;
+ }
+
+ public String getBinaryName() {
+ return binaryName;
+ }
+
+ private void inferSemiQualifiedName() {
+ semiQualifiedName = binaryName.replace('/', '.');
+ }
+
+ private void inferQualifiedName() {
+ if (simpleName == null) {
+ qualifiedName = semiQualifiedName.replace('$', '.');
+ } else {
+ int prefixLength = semiQualifiedName.length() - simpleName.length();
+ String prefix = semiQualifiedName.substring(0, prefixLength);
+ qualifiedName = prefix.replace('$', '.') + simpleName;
+ }
+ }
+
+ private void inferSimpleName() {
+ int i = qualifiedName.lastIndexOf('.');
+ if (i == -1) {
+ simpleName = qualifiedName;
+ } else {
+ simpleName = qualifiedName.substring(i + 1);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ClassName
+ && binaryName.equals(((ClassName) obj).binaryName)
+ && declaredName.equals(((ClassName) obj).declaredName);
+ }
+
+ @Override
+ public int hashCode() {
+ return binaryName.hashCode() ^ declaredName.hashCode();
+ }
+
+ /**
+ * Returns {@code true} if {@code name} is <em>syntactically</em> a
+ * valid simple name.
+ */
+ public static boolean isSimpleName(String name) {
+ if (name == null || name.isEmpty()) {
+ return false;
+ }
+ if (!Character.isJavaIdentifierStart(name.charAt(0))) {
+ return false;
+ }
+ int len = name.length();
+ for (int i = 1; i < len; ++i) {
+ if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected static final Predicate<String> IS_SIMPLE_NAME =
+ new Predicate<String>() {
+ @Override
+ public boolean apply(String name) {
+ return isSimpleName(name);
+ }
+ };
+
+ public static Predicate<String> isSimpleName() {
+ return IS_SIMPLE_NAME;
+ }
+
+ /**
+ * Returns {@code true} if {@code name} is <em>syntactically</em> a
+ * valid qualified name.
+ *
+ * <p>Note: there is no syntactical difference between
+ * semi-qualified and fully-qualified names.
+ */
+ @Ensures("result == isBinaryName(name.replace('.', '/'))")
+ public static boolean isQualifiedName(String name) {
+ if (name == null || name.isEmpty()) {
+ return false;
+ }
+ String[] parts = name.split("\\.");
+ for (String part : parts) {
+ if (!isSimpleName(part)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static final Predicate<String> IS_QUALIFIED_NAME =
+ new Predicate<String>() {
+ @Override
+ public boolean apply(String name) {
+ return isQualifiedName(name);
+ }
+ };
+
+ public static Predicate<String> isQualifiedName() {
+ return IS_QUALIFIED_NAME;
+ }
+
+ /**
+ * Returns {@code true} if {@code name} is <em>syntactically</em> a
+ * valid qualified name followed by the two characters {@code .*}.
+ */
+ public static boolean isStarQualifiedName(String name) {
+ return name != null
+ && name.endsWith(".*")
+ && isQualifiedName(name.substring(0, name.length() - 2));
+ }
+
+ /**
+ * Returns {@code true} if {@code name} is <em>syntactically</em> a
+ * valid binary name.
+ */
+ @Ensures("result == isQualifiedName(name.replace('/', '.'))")
+ public static boolean isBinaryName(String name) {
+ if (name == null || name.isEmpty()) {
+ return false;
+ }
+ String[] parts = name.split("/");
+ for (String part : parts) {
+ if (!isSimpleName(part)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static final Predicate<String> IS_BINARY_NAME =
+ new Predicate<String>() {
+ @Override
+ public boolean apply(String name) {
+ return isBinaryName(name);
+ }
+ };
+
+ public static Predicate<String> isBinaryName() {
+ return IS_BINARY_NAME;
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ContractAnnotationModel.java b/src/com/google/java/contract/core/model/ContractAnnotationModel.java
new file mode 100644
index 0000000..424656e
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ContractAnnotationModel.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A model element representing one of the com.google.java.contract.* annotations.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant({
+ "!isWeakVirtual() || isVirtual()",
+ "getValues() != null",
+ "!getValues().contains(null)",
+ "Iterables.all(getLineNumbers(), " +
+ "Predicates.or(Predicates.isNull(), Predicates.between(1L, null)))",
+ "getValues().size() == getLineNumbers().size()",
+ "getOwner() != null"
+})
+public class ContractAnnotationModel extends ElementModel {
+ /**
+ * {@code true} if this annotation is directly present on the
+ * annotated element, that is, it is not inherited through any of
+ * its interfaces or its superclass.
+ */
+ protected boolean primary;
+
+ /**
+ * {@code true} if this annotation denotes a virtual contract. A
+ * virtual contract is one that is inherited through the normal
+ * (virtual) class hierarchy, and not through an interface.
+ */
+ protected boolean virtual;
+
+ /**
+ * {@code true} if this annotation denotes a contract that is
+ * virtual but does not require a stub to be generated.
+ */
+ protected boolean weakVirtual;
+
+ /**
+ * The name of the owner type of this annotation, from which it is
+ * inherited.
+ */
+ protected ClassName owner;
+
+ /**
+ * The return type of the method contracted by this annotation; it
+ * is required for stubbing since return types may change
+ * covariantly with subtyping in Java.
+ */
+ protected TypeName returnType;
+
+ /**
+ * The values of this annotation. These are assertion expressions,
+ * as strings.
+ */
+ protected List<String> values;
+
+ /**
+ * The line numbers associated with the values of this expression,
+ * in the original source file (each line number may be
+ * {@code null}).
+ */
+ protected List<Long> lineNumbers;
+
+ /**
+ * Constructs a new ContractAnnotationModel.
+ *
+ * @param kind the kind of this element
+ * @param primary whether the annotation is directly present on the
+ * annotated element
+ * @param virtual {@code true} if the contract introduced by this
+ * annotation requires a stub
+ * @param owner the name of the class where this annotation is
+ * inherited from
+ * @param returnType the return type of the contracted method,
+ * if any
+ */
+ @Requires({
+ "kind != null",
+ "owner != null",
+ "kind.isSourceAnnotation()"
+ })
+ public ContractAnnotationModel(ElementKind kind, boolean primary,
+ boolean virtual, ClassName owner,
+ TypeName returnType) {
+ super(kind, "<@" + kind.name() + ">");
+ this.primary = primary;
+ this.virtual = virtual;
+ weakVirtual = false;
+ this.owner = owner;
+ this.returnType = returnType;
+ values = new ArrayList<String>();
+ lineNumbers = new ArrayList<Long>();
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public ContractAnnotationModel(ContractAnnotationModel that) {
+ super(that);
+ primary = that.primary;
+ virtual = that.virtual;
+ weakVirtual = that.weakVirtual;
+ owner = that.owner;
+ returnType = that.returnType;
+ values = new ArrayList<String>(that.values);
+ lineNumbers = new ArrayList<Long>(that.lineNumbers);
+ }
+
+ @Override
+ public ContractAnnotationModel clone() {
+ return new ContractAnnotationModel(this);
+ }
+
+ public boolean isPrimary() {
+ return primary;
+ }
+
+ public boolean isVirtual() {
+ return virtual;
+ }
+
+ public boolean isWeakVirtual() {
+ return weakVirtual;
+ }
+
+ public void setWeakVirtual(boolean weakVirtual) {
+ this.weakVirtual = weakVirtual;
+ }
+
+ public ClassName getOwner() {
+ return owner;
+ }
+
+ public TypeName getReturnType() {
+ return returnType;
+ }
+
+ public List<String> getValues() {
+ return Collections.unmodifiableList(values);
+ }
+
+ public List<Long> getLineNumbers() {
+ return Collections.unmodifiableList(lineNumbers);
+ }
+
+ @Ensures({
+ "getValues().isEmpty()",
+ "getLineNumbers().isEmpty()"
+ })
+ public void clearValues() {
+ values.clear();
+ lineNumbers.clear();
+ }
+
+ @Requires("value != null")
+ @Ensures({
+ "getValues().size() == old(getValues().size()) + 1",
+ "getValues().contains(value.replace('\\r', ' ').replace('\\n', ' '))",
+ "getLineNumbers().size() == old(getLineNumbers().size()) + 1",
+ "getLineNumbers().contains(lineNumber)"
+ })
+ public void addValue(String value, Long lineNumber) {
+ values.add(value.replace('\r', ' ').replace('\n', ' '));
+ lineNumbers.add(lineNumber);
+ }
+
+ /**
+ * Returns {@code true} if the specified argument is equal to this
+ * object. Two ContractAnnotationModel objects are equal if they
+ * are of the same kind and have the same values.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof ContractAnnotationModel)) {
+ return false;
+ }
+
+ ContractAnnotationModel annotation = (ContractAnnotationModel) obj;
+ return annotation.getKind() == getKind()
+ && annotation.getValues().equals(getValues());
+ }
+
+ @Override
+ public void accept(ElementVisitor visitor) {
+ visitor.visitContractAnnotation(this);
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ContractKind.java b/src/com/google/java/contract/core/model/ContractKind.java
new file mode 100644
index 0000000..c3c5ea7
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ContractKind.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+
+/**
+ * The kind of a contract method. This information is at the bytecode
+ * level. There are more contract method kinds than there are
+ * annotations; some of them, such as {@link #OLD} and
+ * {@link SIGNAL_OLD} are used for internal purposes.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public enum ContractKind {
+ /**
+ * A precondition contract method, which evaluates all direct
+ * (non-inherited) preconditions of the target method.
+ */
+ PRE,
+
+ /**
+ * A postcondition contract method, which evaluates all direct
+ * (non-inherited) postconditions of the target method.
+ */
+ POST,
+
+ /**
+ * An exceptional postcondition contract method, which evaluates all
+ * direct (non-inherited) exceptional postconditions of the target
+ * method.
+ */
+ SIGNAL,
+
+ /**
+ * An invariant contract method, which evaluates all direct
+ * (non-inherited) invariants of the target class.
+ */
+ INVARIANT,
+
+ /**
+ * An old value contract method, which computes one old value
+ * expression for the corresponding postcondition contract method.
+ */
+ OLD,
+
+ /**
+ * An exceptional old value contract method, which computes one old
+ * value expression for the corresponding exceptional postcondition
+ * contract method.
+ */
+ SIGNAL_OLD,
+
+ /**
+ * A synthetic access method, generated by the Java compiler, used
+ * in contract methods.
+ */
+ ACCESS,
+
+ /**
+ * A contract helper, for indirect contract evaluation.
+ *
+ * @see ContractCreator
+ */
+ HELPER;
+
+ /**
+ * Returns {@code true} if this kind denotes a contract method that
+ * applies to a class.
+ */
+ @Ensures("result == (!isMethodContract() && !isHelperContract())")
+ public boolean isClassContract() {
+ switch (this) {
+ case INVARIANT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns {@code true} if this kind denotes a contract method that
+ * applies to a method.
+ */
+ @Ensures("result == (!isClassContract() && !isHelperContract())")
+ public boolean isMethodContract() {
+ switch (this) {
+ case PRE:
+ case POST:
+ case SIGNAL:
+ case OLD:
+ case SIGNAL_OLD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns {@code true} if this kind denotes a helper contract
+ * method, called by other contract methods.
+ */
+ @Ensures("result == (!isClassContract() && !isMethodContract())")
+ public boolean isHelperContract() {
+ return !isClassContract() && !isMethodContract();
+ }
+
+ /**
+ * Returns {@code true} if this kind denotes a postcondition (normal
+ * or exceptional).
+ */
+ @Ensures({
+ "!result || isMethodContract()",
+ "!(result && isOld())"
+ })
+ public boolean isPostcondition() {
+ switch (this) {
+ case POST:
+ case SIGNAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns {@code true} if this kind denotes a method contract old
+ * computing method values.
+ */
+ @Ensures({
+ "!result || isMethodContract()",
+ "!(result && isPostcondition())"
+ })
+ public boolean isOld() {
+ switch (this) {
+ case OLD:
+ case SIGNAL_OLD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns the kind of the old value contract methods computing old
+ * values for this kind.
+ */
+ @Requires("isPostcondition()")
+ public ContractKind getOldKind() {
+ switch (this) {
+ case POST:
+ return OLD;
+ case SIGNAL:
+ return SIGNAL_OLD;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public boolean hasNameSpace() {
+ return this != HELPER;
+ }
+
+ /**
+ * Returns the name space used when generating contract method names
+ * for this kind.
+ */
+ @Requires("hasNameSpace()")
+ @Ensures("ClassName.isSimpleName(result)")
+ public String getNameSpace() {
+ switch (this) {
+ case PRE:
+ return "com$google$java$contract$P";
+ case POST:
+ return "com$google$java$contract$Q";
+ case SIGNAL:
+ return "com$google$java$contract$E";
+ case INVARIANT:
+ return "com$google$java$contract$I";
+ case OLD:
+ return "com$google$java$contract$QO";
+ case SIGNAL_OLD:
+ return "com$google$java$contract$EO";
+ case ACCESS:
+ return "access";
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Returns the name space used when generating helper contract
+ * method names for this kind.
+ */
+ @Requires("!isHelperContract()")
+ @Ensures("ClassName.isSimpleName(result)")
+ public String getHelperNameSpace() {
+ switch (this) {
+ case PRE:
+ return "com$google$java$contract$PH";
+ case POST:
+ return "com$google$java$contract$QH";
+ case SIGNAL:
+ return "com$google$java$contract$EH";
+ case INVARIANT:
+ return "com$google$java$contract$IH";
+ case OLD:
+ return "com$google$java$contract$QOH";
+ case SIGNAL_OLD:
+ return "com$google$java$contract$EOH";
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Returns the variance of this kind, or {@code null} if not
+ * applicable.
+ */
+ public ContractVariance getVariance() {
+ switch (this) {
+ case PRE:
+ return ContractVariance.CONTRAVARIANT;
+ case POST:
+ case SIGNAL:
+ case INVARIANT:
+ return ContractVariance.COVARIANT;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ContractMethodModel.java b/src/com/google/java/contract/core/model/ContractMethodModel.java
new file mode 100644
index 0000000..7161cca
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ContractMethodModel.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.Elements;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * A model element representing a contract method. Contrary to other
+ * methods, contract methods are not mocked and the model accounts for
+ * the implementation code. This code is stored in text form.
+ */
+ at Invariant({
+ "getContractKind() != null",
+ "getId() >= -1",
+ "getStatements() != null",
+ "!getStatements().contains(null)",
+ "!getContractKind().isClassContract() || getContractedMethod() == null",
+ "!getContractKind().isMethodContract() || getContractedMethod() != null"
+})
+public class ContractMethodModel extends MethodModel {
+ private static final ClassName CONTRACT_SIGNATURE_CLASS =
+ new ClassName("com/google/java/contract/core/ContractMethodSignature");
+
+ /**
+ * The kind of contract this method implements.
+ */
+ protected ContractKind contractKind;
+
+ /**
+ * The ID of this contract method.
+ */
+ protected int id;
+
+ /**
+ * The body of this method, as strings of code.
+ */
+ protected List<String> statements;
+
+ /**
+ * A fixed prologue.
+ */
+ protected String prologue;
+
+ /**
+ * A fixed epilogue.
+ */
+ protected String epilogue;
+
+ /**
+ * The contracted method this contract applies to, or {@code null}
+ * if this is a class-wide contract.
+ */
+ protected MethodModel contractedMethod;
+
+ /**
+ * The line numbers associated with the original source annotation
+ * that specifies this contract.
+ */
+ protected List<Long> lineNumbers;
+
+ /**
+ * Constructs a new ContractMethodModel.
+ *
+ * @param kind the kind of this contract
+ * @param name the name of this contract method
+ * @param returnType the return type of this method
+ * @param contracted the contracted method, if any
+ */
+ @Requires({
+ "kind != null",
+ "name != null",
+ "returnType != null",
+ "!kind.isClassContract() || contracted == null"
+ })
+ public ContractMethodModel(ContractKind kind, String name,
+ TypeName returnType, MethodModel contracted) {
+ super(ElementKind.CONTRACT_METHOD, name, returnType);
+
+ if (contracted != null) {
+ typeParameters = new ArrayList<TypeName>(contracted.getTypeParameters());
+ Elements.copyParameters(this, contracted.getParameters());
+ modifiers = EnumSet.copyOf(contracted.getModifiers());
+ }
+ fixCommonModifiers();
+
+ contractKind = kind;
+ id = -1;
+
+ statements = new ArrayList<String>();
+ prologue = null;
+ epilogue = null;
+
+ contractedMethod = contracted;
+ lineNumbers = null;
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public ContractMethodModel(ContractMethodModel that) {
+ super(that);
+
+ contractKind = that.contractKind;
+ id = that.id;
+
+ statements = new ArrayList<String>(that.statements);
+ prologue = that.prologue;
+ epilogue = that.epilogue;
+
+ contractedMethod = that.contractedMethod;
+ lineNumbers = that.lineNumbers;
+ }
+
+ @Override
+ public ContractMethodModel clone() {
+ return new ContractMethodModel(this);
+ }
+
+ /**
+ * Removes commonly undesirable modifiers that might have been
+ * inherited.
+ */
+ private void fixCommonModifiers() {
+ removeModifier(ElementModifier.ABSTRACT);
+ removeModifier(ElementModifier.TRANSIENT);
+ }
+
+ public ContractKind getContractKind() {
+ return contractKind;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public MethodModel getContractedMethod() {
+ return contractedMethod;
+ }
+
+ public List<Long> getLineNumbers() {
+ return lineNumbers;
+ }
+
+ @Ensures("lineNumbers == getLineNumbers()")
+ public void setLineNumbers(List<Long> lineNumbers) {
+ this.lineNumbers = lineNumbers;
+ }
+
+ @Ensures("result != null")
+ public String getCode() {
+ StringBuilder buffer = new StringBuilder();
+ if (prologue != null) {
+ buffer.append(prologue);
+ }
+ for (String stmt : statements) {
+ buffer.append(stmt);
+ }
+ if (epilogue != null) {
+ buffer.append(epilogue);
+ }
+ return buffer.toString();
+ }
+
+ public List<String> getStatements() {
+ return Collections.unmodifiableList(statements);
+ }
+
+ public String getPrologue() {
+ return prologue;
+ }
+
+ @Ensures("getPrologue().equals(prologue)")
+ public void setPrologue(String prologue) {
+ this.prologue = prologue;
+ }
+
+ public String getEpilogue() {
+ return epilogue;
+ }
+
+ @Ensures("getEpilogue().equals(epilogue)")
+ public void setEpilogue(String epilogue) {
+ this.epilogue = epilogue;
+ }
+
+ @Ensures("getStatements().isEmpty()")
+ public void clearStatements() {
+ statements.clear();
+ }
+
+ @Requires("stmt != null")
+ @Ensures({
+ "getStatements().size() == old(getStatements().size()) + 1",
+ "getStatements().contains(stmt)"
+ })
+ public void addStatement(String stmt) {
+ statements.add(stmt);
+ }
+
+ @Override
+ public void accept(ElementVisitor visitor) {
+ visitor.visitContractMethod(this);
+ }
+
+ @Override
+ public EnumSet<ElementKind> getAllowedEnclosedKinds() {
+ EnumSet<ElementKind> allowed =
+ EnumSet.of(ElementKind.CONTRACT_SIGNATURE);
+ allowed.addAll(super.getAllowedEnclosedKinds());
+ return allowed;
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ContractVariance.java b/src/com/google/java/contract/core/model/ContractVariance.java
new file mode 100644
index 0000000..819d2a0
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ContractVariance.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+/**
+ * The variance of a kind of contract. Contracts are either
+ * contravariant (preconditions), covariant (postconditions and
+ * invariants), or have no variance (represented by {@code null}).
+ *
+ * <p>Multiple contravariant contracts should be combined using the
+ * <em>or</em> combinator. Multiple covariant contracts should be
+ * combined using the <em>and</em> combinator. Contracts that have no
+ * variance should not be combined.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public enum ContractVariance {
+ /**
+ * A contravariant contract.
+ */
+ CONTRAVARIANT,
+
+ /**
+ * A covariant contract.
+ */
+ COVARIANT;
+}
diff --git a/src/com/google/java/contract/core/model/ElementKind.java b/src/com/google/java/contract/core/model/ElementKind.java
new file mode 100644
index 0000000..d1ac5f0
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ElementKind.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Requires;
+
+/**
+ * The kind of an {@link Element}.
+ */
+public enum ElementKind {
+ /*
+ * Source elements.
+ */
+
+ /**
+ * A source file.
+ */
+ SOURCE,
+
+ /**
+ * A class.
+ */
+ CLASS,
+
+ /**
+ * An interface.
+ */
+ INTERFACE,
+
+ /**
+ * An annotation.
+ */
+ ANNOTATION_TYPE,
+
+ /**
+ * An enum.
+ */
+ ENUM,
+
+ /**
+ * An enum constant.
+ */
+ CONSTANT,
+
+ /**
+ * A field. Mocked in output.
+ */
+ FIELD,
+
+ /**
+ * A method that is not a constructor. Mocked in output.
+ */
+ METHOD,
+
+ /**
+ * A constructor. Mocked in output.
+ */
+ CONSTRUCTOR,
+
+ /**
+ * A method parameter.
+ */
+ PARAMETER,
+
+ /**
+ * An @Invariant annotation.
+ */
+ INVARIANT,
+
+ /**
+ * An @Requires annotation. Source-only, not present in output.
+ */
+ REQUIRES,
+
+ /**
+ * An @Ensures annotation. Source-only, not present in output.
+ */
+ ENSURES,
+
+ /**
+ * An @ThrowEnsures annotation. Source-only, not present in output.
+ */
+ THROW_ENSURES,
+
+ /*
+ * Output elements.
+ */
+
+ /**
+ * An @ContractMethodSignature annotation.
+ */
+ CONTRACT_SIGNATURE,
+
+ /**
+ * A contract method.
+ */
+ CONTRACT_METHOD,
+
+ /**
+ * A mock contract method.
+ */
+ CONTRACT_MOCK;
+
+ public boolean isType() {
+ switch (this) {
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case ANNOTATION_TYPE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isMember() {
+ switch (this) {
+ case FIELD:
+ case METHOD:
+ case CONSTRUCTOR:
+ case CONTRACT_METHOD:
+ case CONTRACT_MOCK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Requires("isMember()")
+ public boolean isMock() {
+ switch (this) {
+ case METHOD:
+ case CONSTRUCTOR:
+ case CONTRACT_MOCK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Requires("isMember()")
+ public boolean isContract() {
+ return !isMock();
+ }
+
+ public boolean isAnnotation() {
+ switch (this) {
+ case INVARIANT:
+ case REQUIRES:
+ case ENSURES:
+ case THROW_ENSURES:
+ case CONTRACT_SIGNATURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isInterfaceType() {
+ switch(this) {
+ case INTERFACE:
+ case ANNOTATION_TYPE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isSourceAnnotation() {
+ switch (this) {
+ case INVARIANT:
+ case REQUIRES:
+ case ENSURES:
+ case THROW_ENSURES:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ElementModel.java b/src/com/google/java/contract/core/model/ElementModel.java
new file mode 100644
index 0000000..29279cf
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ElementModel.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicate;
+import com.google.java.contract.util.Predicates;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * An abstract model element.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant({
+ "getEnclosingElement() == null " +
+ "|| getEnclosingElement().getEnclosedElements().contains(this)",
+ "Iterables.all(getEnclosedElements(), isValidEnclosedElement())",
+ "getKind() != null",
+ "getSimpleName() != null"
+})
+public abstract class ElementModel implements Cloneable {
+ /**
+ * The kind of this element.
+ */
+ protected ElementKind kind;
+
+ /**
+ * The simple name of this element. The simple name is the relative
+ * identifier used to refer to this element in source code.
+ *
+ * <p>This name must be kept synchronized with other forms, if any.
+ */
+ protected String simpleName;
+
+ /**
+ * The parent element of this element.
+ */
+ protected ElementModel enclosingElement;
+
+ /**
+ * The child elements of this element. Each kind refines its own
+ * enclosing--enclosed relationship with other kinds.
+ *
+ * <p>This list must contain all enclosed elements but need not be
+ * the only such container. Specialized, partial subsets of enclosed
+ * elements, if any, must be kept synchronized with this field.
+ */
+ protected List<ElementModel> enclosedElements;
+
+ /**
+ * An object intended to hold information on the source of this
+ * model element.
+ */
+ protected Object sourceInfo;
+
+ /**
+ * Constructs a new ElementModel of the specified kind, with the
+ * specified simple name.
+ */
+ @Requires({
+ "kind != null",
+ "name != null"
+ })
+ protected ElementModel(ElementKind kind, String name) {
+ this.kind = kind;
+ simpleName = name;
+ enclosingElement = null;
+ enclosedElements = new ArrayList<ElementModel>();
+ sourceInfo = null;
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ protected ElementModel(ElementModel that) {
+ kind = that.kind;
+ simpleName = that.simpleName;
+ enclosingElement = null;
+ enclosedElements =
+ new ArrayList<ElementModel>(that.enclosedElements.size());
+ ArrayList<ElementModel> elements =
+ new ArrayList<ElementModel>(that.enclosedElements);
+ for (ElementModel element : elements) {
+ try {
+ addEnclosedElement(element.clone());
+ } catch (CloneNotSupportedException e) {
+ /*
+ * Not reached. Only abstract classes in the hierarchy will
+ * throw CloneNotSupportedException.
+ */
+ }
+ }
+ sourceInfo = that.sourceInfo;
+ }
+
+ @Override
+ @Ensures({
+ "result != null",
+ "result.getEnclosingElement() == null"
+ })
+ protected ElementModel clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+
+ public ElementKind getKind() {
+ return kind;
+ }
+
+ public List<? extends ElementModel> getEnclosedElements() {
+ return Collections.unmodifiableList(enclosedElements);
+ }
+
+ /**
+ * Adds a child element at the specified position in the enclosed
+ * element list. First, the added element is detached from its
+ * previous parent, if any.
+ */
+ @Requires({
+ "index >= 0",
+ "index <= element.getEnclosedElements().size()",
+ "isValidEnclosedElement(element)"
+ })
+ @Ensures("enclosedElements.contains(element)")
+ protected void addEnclosedElement(int index, ElementModel element) {
+ fixEnclosedElement(element);
+ enclosedElements.add(index, element);
+ }
+
+ /**
+ * Adds a child element to the enclosed element list. First, the
+ * added element is detached from its previous parent, if any.
+ */
+ @Requires("isValidEnclosedElement(element)")
+ @Ensures("getEnclosedElements().contains(element)")
+ public void addEnclosedElement(ElementModel element) {
+ fixEnclosedElement(element);
+ enclosedElements.add(element);
+ }
+
+ private void fixEnclosedElement(ElementModel element) {
+ if (element.enclosingElement != null) {
+ element.enclosingElement.removeEnclosedElement(element);
+ }
+ element.enclosingElement = this;
+ }
+
+ @Requires("isValidEnclosedElement(element)")
+ @Ensures("!getEnclosedElements().contains(element)")
+ public void removeEnclosedElement(ElementModel element) {
+ enclosedElements.remove(element);
+ element.enclosingElement = null;
+ }
+
+ public ElementModel getEnclosingElement() {
+ return enclosingElement;
+ }
+
+ @Requires("element == null || isValidEnclosedElement(element)")
+ @Ensures({
+ "element == getEnclosingElement()",
+ "element.getEnclosedElements().contains(this)"
+ })
+ public void setEnclosingElement(ElementModel element) {
+ element.addEnclosedElement(this);
+ }
+
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ public Object getSourceInfo() {
+ return sourceInfo;
+ }
+
+ public void setSourceInfo(Object info) {
+ sourceInfo = info;
+ }
+
+ /**
+ * Visits this element with the specified visitor.
+ */
+ @Requires("visitor != null")
+ public abstract void accept(ElementVisitor visitor);
+
+ /**
+ * Returns {@code true} if the specified argument is equal to this
+ * object. Two ElementModel are equal if they have the same simple
+ * name and their enclosing elements are equal.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof ElementModel)) {
+ return false;
+ }
+
+ ElementModel element = (ElementModel) obj;
+ return element.getSimpleName().equals(getSimpleName())
+ && element.getEnclosingElement().equals(getEnclosingElement());
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * simpleName.hashCode() + getEnclosingElement().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return simpleName;
+ }
+
+ /**
+ * Returns {@code true} if {@code element} can be added (as an
+ * enclosed element) to this element.
+ */
+ public boolean isValidEnclosedElement(ElementModel element) {
+ return element != null
+ && getAllowedEnclosedKinds().contains(element.getKind());
+ }
+
+ private final Predicate<ElementModel> isValidEnclosedElementPredicate =
+ new Predicate<ElementModel>() {
+ @Override
+ public boolean apply(ElementModel element) {
+ return isValidEnclosedElement(element);
+ }
+ };
+
+ public Predicate<ElementModel> isValidEnclosedElement() {
+ return isValidEnclosedElementPredicate;
+ }
+
+ /**
+ * Reflectively returns the kinds of elements that are allowed as
+ * children of this element.
+ */
+ @Ensures("result != null")
+ public EnumSet<ElementKind> getAllowedEnclosedKinds() {
+ return EnumSet.noneOf(ElementKind.class);
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ElementModifier.java b/src/com/google/java/contract/core/model/ElementModifier.java
new file mode 100644
index 0000000..5eb21fa
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ElementModifier.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+
+import java.util.EnumSet;
+import java.util.Set;
+import javax.lang.model.element.Modifier;
+
+/**
+ * A Java declaration modifier. In general, modifiers can be applied
+ * to types, fields, and methods.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public enum ElementModifier {
+ /**
+ * The public modifier.
+ */
+ PUBLIC,
+
+ /**
+ * The protected modifier.
+ */
+ PROTECTED,
+
+ /**
+ * The package private modifier. This is the default visibility.
+ */
+ PACKAGE_PRIVATE,
+
+ /**
+ * The private modifier.
+ */
+ PRIVATE,
+
+ /**
+ * The modifier static.
+ */
+ STATIC,
+
+ /**
+ * The final modifier.
+ */
+ FINAL,
+
+ /**
+ * The volatile modifier.
+ */
+ VOLATILE,
+
+ /**
+ * The transient modifier .
+ */
+ TRANSIENT,
+
+ /**
+ * The abstract modifier.
+ */
+ ABSTRACT,
+
+ /**
+ * The synchronized modifier.
+ */
+ SYNCHRONIZED,
+
+ /**
+ * The native modifier.
+ */
+ NATIVE;
+
+ public boolean isVisibility() {
+ switch (this) {
+ case PUBLIC:
+ case PROTECTED:
+ case PACKAGE_PRIVATE:
+ case PRIVATE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Requires("modifiers != null")
+ public static ElementModifier visibilityIn(
+ EnumSet<ElementModifier> modifiers) {
+ for (ElementModifier modifier : modifiers) {
+ if (modifier.isVisibility()) {
+ return modifier;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts a set of {@link Modifier} objects into a set of
+ * {@link ElementModifier} objects.
+ */
+ @Requires("modifiers != null")
+ @Ensures("result != null")
+ public static EnumSet<ElementModifier> forModifiers(
+ Set<? extends Modifier> modifiers){
+ EnumSet<ElementModifier> result = EnumSet.noneOf(ElementModifier.class);
+
+ if (!modifiers.contains(Modifier.PUBLIC)
+ && !modifiers.contains(Modifier.PROTECTED)
+ && !modifiers.contains(Modifier.PRIVATE)) {
+ result.add(ElementModifier.PACKAGE_PRIVATE);
+ }
+
+ for (Modifier modifier : modifiers) {
+ switch (modifier) {
+ case PUBLIC:
+ result.add(ElementModifier.PUBLIC);
+ break;
+ case PROTECTED:
+ result.add(ElementModifier.PROTECTED);
+ break;
+ case PRIVATE:
+ result.add(ElementModifier.PRIVATE);
+ break;
+ case STATIC:
+ result.add(ElementModifier.STATIC);
+ break;
+ case FINAL:
+ result.add(ElementModifier.FINAL);
+ break;
+ case VOLATILE:
+ result.add(ElementModifier.VOLATILE);
+ break;
+ case TRANSIENT:
+ result.add(ElementModifier.TRANSIENT);
+ break;
+ case ABSTRACT:
+ result.add(ElementModifier.ABSTRACT);
+ break;
+ case SYNCHRONIZED:
+ result.add(ElementModifier.SYNCHRONIZED);
+ break;
+ case NATIVE:
+ result.add(ElementModifier.NATIVE);
+ break;
+ default:
+ /* Unsupported modifier. */
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the Java source code representation of this modifier, or
+ * the empty string if none.
+ */
+ @Override
+ public String toString() {
+ switch (this) {
+ case PUBLIC:
+ case PROTECTED:
+ case PRIVATE:
+ case STATIC:
+ case FINAL:
+ case VOLATILE:
+ case ABSTRACT:
+ case SYNCHRONIZED:
+ return name().toLowerCase();
+ default:
+ return "";
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/model/ElementVisitor.java b/src/com/google/java/contract/core/model/ElementVisitor.java
new file mode 100644
index 0000000..a4a1745
--- /dev/null
+++ b/src/com/google/java/contract/core/model/ElementVisitor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Requires;
+
+/**
+ * A visitor of model elements.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public interface ElementVisitor {
+ /**
+ * Visits a {@link TypeModel} object.
+ */
+ @Requires("type != null")
+ public void visitType(TypeModel type);
+
+ /**
+ * Visits a {@link VariableModel} object.
+ */
+ @Requires("variable != null")
+ public void visitVariable(VariableModel variable);
+
+ /**
+ * Visits an {@link MethodModel} object.
+ */
+ @Requires("method != null")
+ public void visitMethod(MethodModel method);
+
+ /**
+ * Visits a {@link ContractMethodModel} object.
+ */
+ @Requires("contract != null")
+ public void visitContractMethod(ContractMethodModel contract);
+
+ /**
+ * Visits a {@link ContractAnnotationModel} object.
+ */
+ @Requires("annotation != null")
+ public void visitContractAnnotation(ContractAnnotationModel annotation);
+}
diff --git a/src/com/google/java/contract/core/model/GenericElementModel.java b/src/com/google/java/contract/core/model/GenericElementModel.java
new file mode 100644
index 0000000..785bb52
--- /dev/null
+++ b/src/com/google/java/contract/core/model/GenericElementModel.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An abstract model element representing an element that can accept
+ * type parameters.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "getTypeParameters() != null",
+ "!getTypeParameters().contains(null)"
+})
+public abstract class GenericElementModel extends QualifiedElementModel {
+ /**
+ * The list of type parameters of this type; empty if not a generic
+ * type.
+ */
+ protected List<TypeName> typeParameters;
+
+ /**
+ * Constructs a new TypeModel of the specified kind, with the
+ * specified name.
+ */
+ @Requires({
+ "kind != null",
+ "name != null"
+ })
+ protected GenericElementModel(ElementKind kind, String name) {
+ super(kind, name);
+ typeParameters = new ArrayList<TypeName>();
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ protected GenericElementModel(GenericElementModel that) {
+ super(that);
+ typeParameters = new ArrayList<TypeName>(that.typeParameters);
+ }
+
+ @Override
+ protected GenericElementModel clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+
+ public List<? extends TypeName> getTypeParameters() {
+ return Collections.unmodifiableList(typeParameters);
+ }
+
+ @Ensures("getTypeParameters().isEmpty()")
+ public void clearTypeParameters() {
+ typeParameters.clear();
+ }
+
+ @Requires("typeName != null")
+ @Ensures({
+ "getTypeParameters().size() == old(getTypeParameters().size()) + 1",
+ "getTypeParameters().contains(typeName)"
+ })
+ public void addTypeParameter(TypeName typeName) {
+ typeParameters.add(typeName);
+ }
+}
diff --git a/src/com/google/java/contract/core/model/HelperTypeModel.java b/src/com/google/java/contract/core/model/HelperTypeModel.java
new file mode 100644
index 0000000..9219ef3
--- /dev/null
+++ b/src/com/google/java/contract/core/model/HelperTypeModel.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.JavaUtils;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * A helper type, used to implement interface contracts.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class HelperTypeModel extends TypeModel {
+ /**
+ * Constructs a new HelperTypeModel for the interface type
+ * {@code original}.
+ */
+ @Requires({
+ "original != null",
+ "original.getKind() == ElementKind.INTERFACE"
+ })
+ public HelperTypeModel(TypeModel original) {
+ super(original);
+
+ kind = ElementKind.CLASS;
+
+ String oldQualifiedName = name.getQualifiedName();
+ String newQualifiedName = oldQualifiedName + JavaUtils.HELPER_CLASS_SUFFIX;
+ String newDeclaredName =
+ name.getDeclaredName().replace(oldQualifiedName, newQualifiedName);
+ name = new ClassName(name.getBinaryName() + JavaUtils.HELPER_CLASS_SUFFIX,
+ newDeclaredName,
+ name.getSimpleName() + JavaUtils.HELPER_CLASS_SUFFIX);
+ simpleName = name.getSimpleName();
+
+ interfaces = Collections.singleton(original.getName());
+ superArguments = Collections.emptyList();
+
+ Iterator<ElementModel> iter = enclosedElements.iterator();
+ while (iter.hasNext()) {
+ if (iter.next().getKind().isType()) {
+ iter.remove();
+ }
+ }
+ }
+
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public HelperTypeModel(HelperTypeModel that) {
+ super(that);
+ }
+
+ @Override
+ public HelperTypeModel clone() {
+ return new HelperTypeModel(this);
+ }
+}
diff --git a/src/com/google/java/contract/core/model/MethodModel.java b/src/com/google/java/contract/core/model/MethodModel.java
new file mode 100644
index 0000000..9b6d302
--- /dev/null
+++ b/src/com/google/java/contract/core/model/MethodModel.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.Elements;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A model element representing a method.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "getExceptions() != null",
+ "isConstructor() == (returnType == null)"
+})
+public class MethodModel extends GenericElementModel {
+ /**
+ * The list of exceptions that can be thrown by this method.
+ */
+ protected Set<TypeName> exceptions;
+
+ /**
+ * The return type of this method, or {@code null} if this is a
+ * constructor.
+ */
+ protected TypeName returnType;
+
+ /**
+ * Whether this method accepts variable-length argument lists.
+ */
+ protected boolean variadic;
+
+ /**
+ * Constructs a new MethodModel of the specified kind, which is not
+ * a constructor, with the specified name and return type.
+ */
+ @Requires({
+ "kind != null",
+ "kind.isMember() && kind != ElementKind.FIELD",
+ "kind != ElementKind.CONSTRUCTOR",
+ "name != null",
+ "returnType != null"
+ })
+ public MethodModel(ElementKind kind, String name, TypeName returnType) {
+ super(kind, name);
+ exceptions = new HashSet<TypeName>();
+ this.returnType = returnType;
+ variadic = false;
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public MethodModel(MethodModel that) {
+ super(that);
+ exceptions = new HashSet<TypeName>(that.exceptions);
+ returnType = that.returnType;
+ variadic = false;
+ }
+
+ @Override
+ public MethodModel clone() {
+ return new MethodModel(this);
+ }
+
+ /**
+ * Constructs a new constructor model.
+ */
+ public MethodModel() {
+ super(ElementKind.CONSTRUCTOR, "<init>");
+ exceptions = new HashSet<TypeName>();
+ returnType = null;
+ variadic = false;
+ }
+
+ public Set<? extends TypeName> getExceptions() {
+ return Collections.unmodifiableSet(exceptions);
+ }
+
+ @Requires("exception != null")
+ @Ensures("getExceptions().contains(exception)")
+ public void addException(TypeName exception) {
+ exceptions.add(exception);
+ }
+
+ @Requires("exception != null")
+ @Ensures("!getExceptions().contains(exception)")
+ public void removeException(TypeName exception) {
+ exceptions.remove(exception);
+ }
+
+ @Requires("!isConstructor()")
+ @Ensures("result != null")
+ public TypeName getReturnType() {
+ return returnType;
+ }
+
+ @Requires({
+ "!isConstructor()",
+ "type != null"
+ })
+ public void setReturnType(TypeName type) {
+ returnType = type;
+ }
+
+ public boolean isVariadic() {
+ return variadic;
+ }
+
+ public void setVariadic(boolean variadic) {
+ this.variadic = variadic;
+ }
+
+ public boolean isConstructor() {
+ return kind == ElementKind.CONSTRUCTOR;
+ }
+
+ @Ensures("result != null")
+ public List<? extends VariableModel> getParameters() {
+ return Elements.filter(getEnclosedElements(), VariableModel.class,
+ ElementKind.PARAMETER);
+ }
+
+ @Requires({
+ "param != null",
+ "param.getKind() == ElementKind.PARAMETER"
+ })
+ @Ensures({
+ "getEnclosedElements().contains(param)",
+ "getParameters().contains(param)"
+ })
+ public void addParameter(VariableModel param) {
+ addEnclosedElement(param);
+ }
+
+ @Requires({
+ "param != null",
+ "param.getKind() == ElementKind.PARAMETER"
+ })
+ @Ensures({
+ "!getEnclosedElements().contains(param)",
+ "!getParameters().contains(param)"
+ })
+ public void removeParameter(VariableModel param) {
+ removeEnclosedElement(param);
+ }
+
+ @Override
+ public void accept(ElementVisitor visitor) {
+ visitor.visitMethod(this);
+ }
+
+ @Override
+ public EnumSet<ElementKind> getAllowedEnclosedKinds() {
+ EnumSet<ElementKind> allowed =
+ EnumSet.of(ElementKind.PARAMETER,
+ ElementKind.REQUIRES,
+ ElementKind.ENSURES,
+ ElementKind.THROW_ENSURES);
+ allowed.addAll(super.getAllowedEnclosedKinds());
+ return allowed;
+ }
+
+ /**
+ * Returns {@code true} if the specified argument is equal to this
+ * object. Two MethodModel objects are equal if they have the same
+ * simple name and parameter types and their enclosing elements are
+ * equal. Equality for MethodModel objects is loose: two methods can
+ * be <em>unequal</em> and still conflict with one another; this
+ * implementation only guarantees that objects representing
+ * different methods will not be compared equal.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof MethodModel)) {
+ return false;
+ }
+
+ MethodModel method = (MethodModel) obj;
+ if (!method.getSimpleName().equals(getSimpleName())) {
+ return false;
+ }
+ List<? extends VariableModel> params = getParameters();
+ List<? extends VariableModel> others = method.getParameters();
+ if (params.size() != others.size()) {
+ return false;
+ }
+ Iterator<? extends VariableModel> itParams = params.iterator();
+ Iterator<? extends VariableModel> itOthers = others.iterator();
+ while (itParams.hasNext()) {
+ if (!itParams.next().getType().equals(itOthers.next().getType())) {
+ return false;
+ }
+ }
+ return method.getEnclosingElement().equals(getEnclosingElement());
+ }
+}
diff --git a/src/com/google/java/contract/core/model/QualifiedElementModel.java b/src/com/google/java/contract/core/model/QualifiedElementModel.java
new file mode 100644
index 0000000..8ca7dd4
--- /dev/null
+++ b/src/com/google/java/contract/core/model/QualifiedElementModel.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.util.EnumSet;
+
+/**
+ * An abstract model element that can be qualified with modifiers.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("getModifiers() != null")
+public abstract class QualifiedElementModel extends ElementModel {
+ /**
+ * The modifiers that apply to this element.
+ */
+ protected EnumSet<ElementModifier> modifiers;
+
+ /**
+ * Constructs a new QualifiedElementModel of the specified kind,
+ * with the specified simple name.
+ */
+ @Requires({
+ "kind != null",
+ "name != null"
+ })
+ protected QualifiedElementModel(ElementKind kind, String name) {
+ super(kind, name);
+ modifiers = EnumSet.noneOf(ElementModifier.class);
+ }
+
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ protected QualifiedElementModel(QualifiedElementModel that) {
+ super(that);
+ modifiers = EnumSet.copyOf(that.modifiers);
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Override
+ protected QualifiedElementModel clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+
+ public EnumSet<ElementModifier> getModifiers() {
+ return modifiers;
+ }
+
+ /**
+ * Adds a modifier to this element. This method ensures that exactly
+ * one visibility modifier is set.
+ */
+ @Requires("modifier != null")
+ @Ensures("getModifiers().contains(modifier)")
+ public void addModifier(ElementModifier modifier) {
+ switch (modifier) {
+ case PRIVATE:
+ case PACKAGE_PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ modifiers.remove(ElementModifier.PRIVATE);
+ modifiers.remove(ElementModifier.PACKAGE_PRIVATE);
+ modifiers.remove(ElementModifier.PROTECTED);
+ modifiers.remove(ElementModifier.PUBLIC);
+ }
+ modifiers.add(modifier);
+ }
+
+ /**
+ * Removes a modifier from this element. This method ensures that
+ * exactly one visibility modifier is set. It defaults to
+ * package-private if no other visibility is set.
+ */
+ @Requires("modifier != null")
+ @Ensures("!getModifiers().contains(modifier)")
+ public void removeModifier(ElementModifier modifier) {
+ modifiers.remove(modifier);
+ switch (modifier) {
+ case PRIVATE:
+ case PACKAGE_PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ modifiers.add(ElementModifier.PACKAGE_PRIVATE);
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/model/TypeModel.java b/src/com/google/java/contract/core/model/TypeModel.java
new file mode 100644
index 0000000..f051bf5
--- /dev/null
+++ b/src/com/google/java/contract/core/model/TypeModel.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.Elements;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A model element representing a type.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "getName() != null",
+ "getSimpleName().equals(getName().getSimpleName())",
+ "getInterfaces() != null",
+ "!getInterfaces().contains(null)",
+ "getSuperArguments() != null",
+ "!getSuperArguments().contains(null)",
+ "getSuperclass() != null || superArguments.isEmpty()",
+ "getImportNames() != null",
+ "!getImportNames().contains(null)",
+ "getEnclosingElement() == null || importNames.isEmpty()"
+})
+public class TypeModel extends GenericElementModel {
+ /**
+ * The name of this type.
+ */
+ protected ClassName name;
+
+ /**
+ * The names of interfaces implemented by this type.
+ */
+ protected Set<ClassName> interfaces;
+
+ /**
+ * The name of the superclass of this type, if any.
+ */
+ protected ClassName superclass;
+
+ /**
+ * The types of the parameters to pass to the superclass constructor
+ * call.
+ */
+ protected List<TypeName> superArguments;
+
+ /**
+ * The values of import statements in effect in this source file.
+ */
+ protected Set<String> importNames;
+
+ /**
+ * Constructs a new TypeModel of the specified kind, with the
+ * specified name.
+ */
+ @Requires({
+ "kind != null",
+ "name != null",
+ "kind.isType()"
+ })
+ public TypeModel(ElementKind kind, ClassName name) {
+ super(kind, name.getSimpleName());
+ this.name = name;
+ interfaces = new HashSet<ClassName>();
+ superArguments = new ArrayList<TypeName>();
+ importNames = new HashSet<String>();
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public TypeModel(TypeModel that) {
+ super(that);
+ name = that.name;
+ interfaces = new HashSet<ClassName>(that.interfaces);
+ superArguments = new ArrayList<TypeName>(that.superArguments);
+ importNames = new HashSet<String>(that.importNames);
+ }
+
+ @Override
+ public TypeModel clone() {
+ return new TypeModel(this);
+ }
+
+ public ClassName getName() {
+ return name;
+ }
+
+ @Requires({
+ "getEnclosingElement() == null",
+ "value != null"
+ })
+ @Ensures("getImportNames().contains(value)")
+ public void addImportName(String value) {
+ importNames.add(value);
+ }
+
+ @Requires({
+ "getEnclosingElement() == null",
+ "value != null"
+ })
+ @Ensures("!getImportNames().contains(value)")
+ public void removeImportName(String value) {
+ importNames.remove(value);
+ }
+
+ @Requires("getEnclosingElement() == null")
+ public Set<String> getImportNames() {
+ return Collections.unmodifiableSet(importNames);
+ }
+
+ public ClassName getSuperclass() {
+ return superclass;
+ }
+
+ public void setSuperclass(ClassName className) {
+ superclass = className;
+ }
+
+ @Ensures("result != null")
+ public List<? extends TypeName> getSuperArguments() {
+ return Collections.unmodifiableList(superArguments);
+ }
+
+ @Ensures("getSuperArguments().isEmpty()")
+ public void clearSuperArguments() {
+ superArguments.clear();
+ }
+
+ @Requires("typeName != null")
+ @Ensures({
+ "getSuperArguments().size() == old(getSuperArguments().size()) + 1",
+ "getSuperArguments().contains(typeName)"
+ })
+ public void addSuperArgument(TypeName typeName) {
+ superArguments.add(typeName);
+ }
+
+ public Set<? extends ClassName> getInterfaces() {
+ return Collections.unmodifiableSet(interfaces);
+ }
+
+ @Requires("className != null")
+ @Ensures("getInterfaces().contains(className)")
+ public void addInterface(ClassName className) {
+ interfaces.add(className);
+ }
+
+ @Requires("className != null")
+ @Ensures("!getInterfaces().contains(className)")
+ public void removeInterface(ClassName className) {
+ interfaces.remove(className);
+ }
+
+ @Ensures("result != null")
+ public List<? extends ContractAnnotationModel> getInvariants() {
+ return Elements.filter(enclosedElements, ContractAnnotationModel.class,
+ ElementKind.INVARIANT);
+ }
+
+ @Requires({
+ "annotation != null",
+ "annotation.getKind() == ElementKind.INVARIANT"
+ })
+ @Ensures({
+ "getEnclosedElements().contains(annotation)",
+ "getInvariants().contains(annotation)"
+ })
+ public void addInvariant(ContractAnnotationModel annotation) {
+ addEnclosedElement(annotation);
+ }
+
+ @Requires({
+ "annotation != null",
+ "annotation.getKind() == ElementKind.INVARIANT"
+ })
+ @Ensures({
+ "!getEnclosedElements().contains(annotation)",
+ "!getInvariants().contains(annotation)"
+ })
+ public void removeInvariant(ContractAnnotationModel annotation) {
+ removeEnclosedElement(annotation);
+ }
+
+ @Ensures({
+ "result != null",
+ "!result.contains(null)"
+ })
+ public List<? extends QualifiedElementModel> getMembers() {
+ return Elements.filter(enclosedElements, QualifiedElementModel.class,
+ ElementKind.CLASS,
+ ElementKind.ENUM,
+ ElementKind.INTERFACE,
+ ElementKind.ANNOTATION_TYPE,
+ ElementKind.FIELD,
+ ElementKind.METHOD,
+ ElementKind.CONSTRUCTOR,
+ ElementKind.CONTRACT_METHOD,
+ ElementKind.CONTRACT_MOCK);
+ }
+
+ @Requires({
+ "member != null",
+ "member.getKind().isMember()"
+ })
+ @Ensures({
+ "getEnclosedElements().contains(member)",
+ "getMembers().contains(member)"
+ })
+ public void addMember(QualifiedElementModel member) {
+ addEnclosedElement(member);
+ }
+
+ @Requires({
+ "member != null",
+ "member.getKind().isMember()"
+ })
+ @Ensures({
+ "!getEnclosedElements().contains(member)",
+ "!getMembers().contains(member)"
+ })
+ public void removeInvariant(QualifiedElementModel member) {
+ removeEnclosedElement(member);
+ }
+
+ @Override
+ public void accept(ElementVisitor visitor) {
+ visitor.visitType(this);
+ }
+
+ @Override
+ public EnumSet<ElementKind> getAllowedEnclosedKinds() {
+ EnumSet<ElementKind> allowed =
+ EnumSet.of(ElementKind.CLASS,
+ ElementKind.ENUM,
+ ElementKind.INTERFACE,
+ ElementKind.ANNOTATION_TYPE,
+ ElementKind.CONSTANT,
+ ElementKind.FIELD,
+ ElementKind.METHOD,
+ ElementKind.CONSTRUCTOR,
+ ElementKind.INVARIANT,
+ ElementKind.CONTRACT_METHOD,
+ ElementKind.CONTRACT_MOCK);
+ allowed.addAll(super.getAllowedEnclosedKinds());
+ return allowed;
+ }
+}
diff --git a/src/com/google/java/contract/core/model/TypeName.java b/src/com/google/java/contract/core/model/TypeName.java
new file mode 100644
index 0000000..927dc1a
--- /dev/null
+++ b/src/com/google/java/contract/core/model/TypeName.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+/**
+ * A type name, as it is used in source files.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("declaredName != null")
+public class TypeName {
+ protected String declaredName;
+
+ /**
+ * Constructs a new empty TypeName. This constructor is provided for
+ * convenience; {@link #declaredName} must be set on return from the
+ * child constructor.
+ */
+ protected TypeName() {
+ }
+
+ /**
+ * Constructs a new TypeName from its declared name.
+ */
+ @Requires("declaredName != null")
+ @Ensures("declaredName.equals(getDeclaredName())")
+ public TypeName(String declaredName) {
+ this.declaredName = declaredName;
+ }
+
+ @Ensures("result != null")
+ public String getDeclaredName() {
+ return declaredName;
+ }
+
+ /**
+ * Returns a string representation of this name usable in Java code.
+ */
+ @Override
+ public String toString() {
+ return declaredName;
+ }
+}
diff --git a/src/com/google/java/contract/core/model/VariableModel.java b/src/com/google/java/contract/core/model/VariableModel.java
new file mode 100644
index 0000000..24bed3d
--- /dev/null
+++ b/src/com/google/java/contract/core/model/VariableModel.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.model;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+/**
+ * A model element representing a variable (this classification
+ * includes parameters).
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("type != null")
+public class VariableModel extends QualifiedElementModel {
+ /**
+ * The type of this variable.
+ */
+ protected TypeName type;
+
+ /**
+ * Constructs a new VariableModel of the specified kind and type,
+ * with the specified name.
+ */
+ @Requires({
+ "kind != null",
+ "name != null",
+ "type != null",
+ "kind == ElementKind.CONSTANT " +
+ "|| kind == ElementKind.FIELD || kind == ElementKind.PARAMETER"
+ })
+ public VariableModel(ElementKind kind, String name, TypeName type) {
+ super(kind, name);
+ this.type = type;
+ }
+
+ /**
+ * Constructs a clone of {@code that}. The new object is identical
+ * to the original <em>except</em> it has no enclosing element.
+ */
+ @Requires("that != null")
+ @Ensures("getEnclosingElement() == null")
+ public VariableModel(VariableModel that) {
+ super(that);
+ type = that.type;
+ }
+
+ @Override
+ public VariableModel clone() {
+ return new VariableModel(this);
+ }
+
+ @Ensures("result != null")
+ public TypeName getType() {
+ return type;
+ }
+
+ @Requires("type != null")
+ public void setType(TypeName type) {
+ this.type = type;
+ }
+
+ @Override
+ public void accept(ElementVisitor visitor) {
+ visitor.visitVariable(this);
+ }
+}
diff --git a/src/com/google/java/contract/core/runtime/BlacklistManager.java b/src/com/google/java/contract/core/runtime/BlacklistManager.java
new file mode 100644
index 0000000..561986e
--- /dev/null
+++ b/src/com/google/java/contract/core/runtime/BlacklistManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.runtime;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.util.DebugUtils;
+import com.google.java.contract.core.util.PatternMap;
+
+/**
+ * A process-wide collection of blacklisted classes.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @see com.google.java.contract.ContractEnvironment#ignore(String)
+ */
+ at Invariant("blacklist != null")
+public class BlacklistManager {
+ protected static BlacklistManager instance = null;
+
+ protected PatternMap<Boolean> blacklist = new PatternMap<Boolean>();
+
+ protected BlacklistManager() {
+ blacklist = new PatternMap<Boolean>();
+ blacklist.put("java.*", true);
+ blacklist.put("javax.*", true);
+ blacklist.put("com.sun.*", true);
+ blacklist.put("sun.*", true);
+ }
+
+ public static BlacklistManager getInstance() {
+ if (instance == null) {
+ instance = new BlacklistManager();
+ }
+ return instance;
+ }
+
+ @Requires("pattern != null")
+ @Ensures("isIgnored(pattern)")
+ public synchronized void ignore(String pattern) {
+ DebugUtils.info("activation", pattern + " +blacklist");
+ blacklist.put(pattern, true);
+ }
+
+ @Requires("pattern != null")
+ @Ensures("!isIgnored(pattern)")
+ public synchronized void unignore(String pattern) {
+ DebugUtils.info("activation", pattern + " -blacklist");
+ blacklist.put(pattern, false);
+ }
+
+ @Requires("pattern != null")
+ public synchronized boolean isIgnored(String pattern) {
+ if (pattern.endsWith(".*") && blacklist.isOverriden(pattern)) {
+ return false;
+ }
+ Boolean rule = blacklist.get(pattern);
+ return rule != null && rule;
+ }
+}
diff --git a/src/com/google/java/contract/core/runtime/ContractContext.java b/src/com/google/java/contract/core/runtime/ContractContext.java
new file mode 100644
index 0000000..6e3d0cb
--- /dev/null
+++ b/src/com/google/java/contract/core/runtime/ContractContext.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.runtime;
+
+import java.util.IdentityHashMap;
+
+/**
+ * A helper to evaluate and enable method specifications. At runtime
+ * it is the interface between instrumented bytecode and Contracts for
+ * Java. Each thread has at most one context attached to it.
+ *
+ * <p>The ContractContext is responsible for:
+ *
+ * <ul>
+ * <li>Disabling contract checking inside of contracts.
+ * <li>Storage for failed predicate information.
+ * </ul>
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public class ContractContext {
+ /**
+ * The default size of the {@link #entered} object map.
+ */
+ private static final int ENTERED_DEFAULT_SIZE = 100;
+
+ static ThreadLocal<ContractContext> context =
+ new ThreadLocal<ContractContext>() {
+ @Override
+ protected ContractContext initialValue() {
+ return new ContractContext();
+ }
+ };
+
+ protected boolean busy;
+ protected IdentityHashMap<Object, Void> entered;
+
+ protected ContractContext() {
+ entered = new IdentityHashMap<Object, Void>(ENTERED_DEFAULT_SIZE);
+ }
+
+ /**
+ * Marks the start of a contract evaluation block.
+ *
+ * @return {@code true} if contracts should be evaluated,
+ * {@code false} otherwise
+ */
+ public boolean tryEnterContract() {
+ if (busy) {
+ return false;
+ } else {
+ busy = true;
+ return true;
+ }
+ }
+
+ /**
+ * Marks the start of a contract evaluation block.
+ */
+ public void leaveContract() {
+ busy = false;
+ }
+
+ /**
+ * Queries whether invariants should be checked for the current
+ * method call. If it returns {@code true}, {@link #leave(Object)}
+ * must be called on method exit.
+ *
+ * @return {@code true} if invariants should be evaluated,
+ * {@code false} otherwise
+ */
+ public boolean tryEnter(Object obj) {
+ if (entered.containsKey(obj)) {
+ return false;
+ } else {
+ entered.put(obj, null);
+ return true;
+ }
+ }
+
+ /**
+ * Must be called if and only if {@link #tryEnter(Object)}
+ * previously returned {@code true} for this call frame.
+ */
+ public void leave(Object obj) {
+ entered.remove(obj);
+ }
+
+ /**
+ * Resets the busy state of this context.
+ */
+ public void clear() {
+ busy = false;
+ }
+}
diff --git a/src/com/google/java/contract/core/runtime/ContractRuntime.java b/src/com/google/java/contract/core/runtime/ContractRuntime.java
new file mode 100644
index 0000000..6e1ff1b
--- /dev/null
+++ b/src/com/google/java/contract/core/runtime/ContractRuntime.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 Google Inc.
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.runtime;
+
+import com.google.java.contract.ContractAssertionError;
+
+/**
+ * Utility methods for use in generated contract code.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class ContractRuntime {
+ /**
+ * Retrieves the contract context associated with the current
+ * thread.
+ */
+ public static ContractContext getContext() {
+ return ContractContext.context.get();
+ }
+
+ /**
+ * Resets the contract context and throws this assertion.
+ */
+ public static void raise(ContractAssertionError ex)
+ throws ContractAssertionError {
+ getContext().clear();
+ throw ex;
+ }
+
+ /**
+ * Magically casts the first argument to the type of the second
+ * argument.
+ *
+ * <p>This method is part of the old value type inference trick. It
+ * is called at run time to cast the old value variable (first
+ * argument) to the type of an unevaluated version of the old value
+ * expression (second argument). The "unevaluation" is achieved
+ * through a constant conditional (for example,
+ * {@code true ? null : oldExpression}).
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T magicCast(Object obj, T dummy) {
+ return (T) obj;
+ }
+}
diff --git a/src/com/google/java/contract/core/runtime/RuntimeContractEnvironment.java b/src/com/google/java/contract/core/runtime/RuntimeContractEnvironment.java
new file mode 100644
index 0000000..6353551
--- /dev/null
+++ b/src/com/google/java/contract/core/runtime/RuntimeContractEnvironment.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.runtime;
+
+import com.google.java.contract.ContractEnvironment;
+
+/**
+ * A contract environment running under the Cofoja Java agent.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class RuntimeContractEnvironment implements ContractEnvironment {
+ protected BlacklistManager blacklistManager;
+
+ public RuntimeContractEnvironment() {
+ blacklistManager = BlacklistManager.getInstance();
+ }
+
+ @Override
+ public void enablePreconditions(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disablePreconditions(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enablePostconditions(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disablePostconditions(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enableInvariants(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disableInvariants(String pattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasPreconditionsEnabled(Class<?> clazz) {
+ return false;
+ }
+
+ @Override
+ public boolean hasPreconditionsEnabled(String pattern) {
+ return false;
+ }
+
+ @Override
+ public boolean hasPostconditionsEnabled(Class<?> clazz) {
+ return false;
+ }
+
+ @Override
+ public boolean hasPostconditionsEnabled(String pattern) {
+ return false;
+ }
+
+ @Override
+ public boolean hasInvariantsEnabled(Class<?> clazz) {
+ return false;
+ }
+
+ @Override
+ public boolean hasInvariantsEnabled(String pattern) {
+ return false;
+ }
+
+ @Override
+ public void ignore(String pattern) {
+ blacklistManager.ignore(pattern);
+ }
+
+ @Override
+ public void unignore(String pattern) {
+ blacklistManager.unignore(pattern);
+ }
+
+ @Override
+ public boolean isIgnored(String pattern) {
+ return blacklistManager.isIgnored(pattern);
+ }
+}
diff --git a/src/com/google/java/contract/core/util/BalancedTokenizer.java b/src/com/google/java/contract/core/util/BalancedTokenizer.java
new file mode 100644
index 0000000..b93b771
--- /dev/null
+++ b/src/com/google/java/contract/core/util/BalancedTokenizer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+
+/**
+ * A tokenizer that processes Java balanced expressions. In addition
+ * to the work of a {@link LineNumberingTokenizer}, a
+ * BalancedTokenizer also keeps track of punctuation balancing. It
+ * knows about parentheses, square brackets, and braces.
+ *
+ * <p>It doesn't check uses of angle brackets, because of the added
+ * complexity. This is usually not an issue, though, as, in Java, type
+ * parameters have a very limited syntax.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "stack != null",
+ "!stack.contains(null)"
+})
+public class BalancedTokenizer extends LineNumberingTokenizer {
+ /**
+ * Balanced punctuation stack. Opening punctuationm symbols are
+ * stored in this stack.
+ */
+ protected Deque<Character> stack;
+
+ /**
+ * Constructs a new BalancedTokenizer reading characters from
+ * {@code reader}.
+ */
+ @Requires("reader != null")
+ public BalancedTokenizer(Reader reader) {
+ super(reader);
+ stack = new ArrayDeque<Character>();
+ }
+
+ @Ensures({
+ "result != null",
+ "result.size() == getCurrentLevel()"
+ })
+ public Collection<Character> getStack() {
+ return Collections.unmodifiableCollection(stack);
+ }
+
+ @Ensures({
+ "result >= 0",
+ "result == getStack().size()"
+ })
+ public int getCurrentLevel() {
+ return stack.size();
+ }
+
+ @Requires("\"()[]{}\".indexOf(c) != -1")
+ protected char getMatchingDelimiter(char c) {
+ switch (c) {
+ case '(':
+ return ')';
+ case '[':
+ return ']';
+ case '{':
+ return '}';
+ case ')':
+ return '(';
+ case ']':
+ return '[';
+ case '}':
+ return '{';
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ protected boolean lex() throws IOException {
+ if (!super.lex()) {
+ if (!stack.isEmpty()) {
+ char d = getMatchingDelimiter(stack.pop());
+ errorMessage = "'" + d + "' expected";
+ hasErrors_ = true;
+ return false;
+ }
+ return false;
+ }
+ if (nextToken.kind == TokenKind.SYMBOL) {
+ char c = nextToken.text.charAt(0);
+ switch (c) {
+ case '(':
+ case '[':
+ case '{':
+ stack.push(c);
+ break;
+ case ')':
+ case ']':
+ case '}':
+ String error = null;
+ if (stack.isEmpty()) {
+ error = "unexpected '" + c + "'";
+ } else {
+ char d = getMatchingDelimiter(stack.pop());
+ if (d != c) {
+ error = "'" + d + "' expected";
+ }
+ }
+ if (error != null) {
+ unreadChar(c);
+ nextToken = null;
+
+ errorMessage = error;
+ hasErrors_ = true;
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/google/java/contract/core/util/DebugUtils.java b/src/com/google/java/contract/core/util/DebugUtils.java
new file mode 100644
index 0000000..1972d86
--- /dev/null
+++ b/src/com/google/java/contract/core/util/DebugUtils.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import javax.tools.JavaFileObject;
+
+/**
+ * Utility methods for debugging.
+ */
+public class DebugUtils {
+ private static String dumpDirectory = "contracts_for_java.out";
+
+ private static Map<String, Boolean> loggingEnabled =
+ new HashMap<String, Boolean>();
+
+ @Ensures("result != null")
+ public static String getDumpDirectory() {
+ return dumpDirectory;
+ }
+
+ @Requires("dir != null")
+ @Ensures("getDumpDirectory().equals(dir)")
+ public static void setDumpDirectory(String dir) {
+ dumpDirectory = dir;
+ }
+
+ /**
+ * Dumps the specified Java file (source, class, etc.) to the dump
+ * directory (by default, "contracts_for_java.out" in the current
+ * directory).
+ *
+ * @param name the qualified class name of the file to dump
+ * @param data the file content
+ * @param kind the kind of file to dump
+ */
+ @Requires({
+ "name != null",
+ "data != null",
+ "kind != null"
+ })
+ public static void dump(String name, byte[] data, JavaFileObject.Kind kind) {
+ File f = new File(dumpDirectory + "/" + name + kind.extension);
+ info("dump", "dumping file " + f);
+ f.getParentFile().mkdirs();
+ try {
+ OutputStream out = new FileOutputStream(f);
+ out.write(data);
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ warn("dump", "while dumping " + f + ": " + e.getMessage());
+ }
+ }
+
+ @Requires("facility != null")
+ public static boolean isLoggingEnabled(String facility) {
+ Boolean enabled = loggingEnabled.get(facility);
+ if (enabled == null) {
+ enabled = Boolean.valueOf(
+ System.getProperty("com.google.java.contract.log." + facility, "false"));
+ loggingEnabled.put(facility, enabled);
+ }
+ return enabled;
+ }
+
+ /**
+ * Outputs an informative message regarding contract
+ * activation. Does not bear any contracts itself so as not to
+ * pollute the output.
+ */
+ public static void contractInfo(String message) {
+ if (isLoggingEnabled("contract")) {
+ System.err.println("[com.google.java.contract:contract "
+ + message + "]");
+ }
+ }
+
+ @Requires({
+ "facility != null",
+ "message != null"
+ })
+ public static void info(String facility, String message) {
+ if (isLoggingEnabled(facility)) {
+ System.err.println("[com.google.java.contract:" + facility + " "
+ + message + "]");
+ }
+ }
+
+ @Requires({
+ "facility != null",
+ "message != null"
+ })
+ public static void warn(String facility, String message) {
+ System.err.println("[com.google.java.contract:" + facility + " "
+ + message + "]");
+ }
+
+ @Requires({
+ "facility != null",
+ "message != null"
+ })
+ public static void err(String facility, String message, Throwable cause) {
+ System.err.println("[com.google.java.contract:" + facility + " FATAL ERROR "
+ + message
+ + (cause != null ? " (stack trace follows)" : "")
+ + "]");
+ if (cause != null) {
+ cause.printStackTrace();
+ } else {
+ new Exception().printStackTrace();
+ }
+ System.exit(1);
+ }
+}
diff --git a/src/com/google/java/contract/core/util/ElementScanner.java b/src/com/google/java/contract/core/util/ElementScanner.java
new file mode 100644
index 0000000..51eb7f4
--- /dev/null
+++ b/src/com/google/java/contract/core/util/ElementScanner.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ElementModel;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.VariableModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A scanner that recursively descends into an {@link Element}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public abstract class ElementScanner extends EmptyElementVisitor {
+ @Requires({
+ "elements != null",
+ "!elements.contains(null)"
+ })
+ public void scan(List<? extends ElementModel> elements) {
+ ArrayList<? extends ElementModel> tmp =
+ new ArrayList<ElementModel>(elements);
+ for (ElementModel element : tmp) {
+ element.accept(this);
+ }
+ }
+
+ @Override
+ public void visitType(TypeModel type) {
+ scan(type.getEnclosedElements());
+ }
+
+ @Override
+ public void visitVariable(VariableModel variable) {
+ scan(variable.getEnclosedElements());
+ }
+
+ @Override
+ public void visitMethod(MethodModel method) {
+ scan(method.getEnclosedElements());
+ }
+
+ @Override
+ public void visitContractMethod(ContractMethodModel contract) {
+ scan(contract.getEnclosedElements());
+ }
+
+ @Override
+ public void visitContractAnnotation(ContractAnnotationModel annotation) {
+ scan(annotation.getEnclosedElements());
+ }
+}
diff --git a/src/com/google/java/contract/core/util/Elements.java b/src/com/google/java/contract/core/util/Elements.java
new file mode 100644
index 0000000..9bdc99a
--- /dev/null
+++ b/src/com/google/java/contract/core/util/Elements.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ElementKind;
+import com.google.java.contract.core.model.ElementModel;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.VariableModel;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * Utility methods to ease working with {@link ElementModel} objects.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @author johannes.rieken at gmail.com (Johannes Rieken)
+ */
+public class Elements {
+ /**
+ * Adds a copy of the {@code parameters} to {@code method}.
+ */
+ @Requires({
+ "method != null",
+ "parameters != null",
+ "!parameters.contains(null)"
+ })
+ public static void copyParameters(MethodModel method,
+ List<? extends VariableModel> parameters) {
+ ArrayList<VariableModel> list = new ArrayList<VariableModel>(parameters);
+ for (VariableModel param : list) {
+ method.addParameter(new VariableModel(param));
+ }
+ }
+
+ /**
+ * Returns the sublist of all elements of the specified kinds.
+ */
+ @Requires({
+ "elements != null",
+ "!elements.contains(null)",
+ "clazz != null",
+ "kinds != null"
+ })
+ @SuppressWarnings("unchecked")
+ public static <T extends ElementModel> List<? extends T> filter(
+ List<? extends ElementModel> elements, Class<T> clazz,
+ ElementKind... kinds) {
+ ArrayList<T> result = new ArrayList<T>();
+ List<ElementKind> list = Arrays.asList(kinds);
+ for (ElementModel element : elements) {
+ if (list.contains(element.getKind())
+ && clazz.isAssignableFrom(element.getClass())) {
+ result.add((T) element);
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Returns the closest enclosing element of the specified kind.
+ */
+ @Requires({
+ "element != null",
+ "clazz != null",
+ "kinds != null"
+ })
+ @SuppressWarnings("unchecked")
+ public static <T extends ElementModel> T findEnclosingElement(
+ ElementModel element, Class<T> clazz, ElementKind... kinds) {
+ List<ElementKind> list = Arrays.asList(kinds);
+ for (;;) {
+ element = element.getEnclosingElement();
+ if (element == null) {
+ return null;
+ }
+ if (list.contains(element.getKind())
+ && clazz.isAssignableFrom(element.getClass())) {
+ return (T) element;
+ }
+ }
+ }
+
+ /**
+ * Returns the closest enclosing type of the specified element.
+ */
+ @Requires("element != null")
+ public static TypeModel getTypeOf(ElementModel element) {
+ return findEnclosingElement(element, TypeModel.class,
+ ElementKind.CLASS, ElementKind.ENUM, ElementKind.INTERFACE);
+ }
+
+ /**
+ * Returns a Contracts for Java private URI for the specified class name
+ * and kind.
+ */
+ @Requires({
+ "name != null",
+ "!name.isEmpty()",
+ "kind != null"
+ })
+ public static URI getUriForClass(String name, Kind kind) {
+ try {
+ return new URI("com.google.java.contract://com.google.java.contract/" + name + kind.extension);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/util/EmptyElementVisitor.java b/src/com/google/java/contract/core/util/EmptyElementVisitor.java
new file mode 100644
index 0000000..e5bf11f
--- /dev/null
+++ b/src/com/google/java/contract/core/util/EmptyElementVisitor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.core.model.ContractAnnotationModel;
+import com.google.java.contract.core.model.ContractMethodModel;
+import com.google.java.contract.core.model.ElementVisitor;
+import com.google.java.contract.core.model.MethodModel;
+import com.google.java.contract.core.model.TypeModel;
+import com.google.java.contract.core.model.VariableModel;
+
+/**
+ * An element visitor that does nothing.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public abstract class EmptyElementVisitor implements ElementVisitor {
+ @Override
+ public void visitType(TypeModel type) {
+ }
+
+ @Override
+ public void visitVariable(VariableModel variable) {
+ }
+
+ @Override
+ public void visitMethod(MethodModel method) {
+ }
+
+ @Override
+ public void visitContractMethod(ContractMethodModel contract) {
+ }
+
+ @Override
+ public void visitContractAnnotation(ContractAnnotationModel annotation) {
+ }
+}
diff --git a/src/com/google/java/contract/core/util/JavaTokenizer.java b/src/com/google/java/contract/core/util/JavaTokenizer.java
new file mode 100644
index 0000000..29b8d46
--- /dev/null
+++ b/src/com/google/java/contract/core/util/JavaTokenizer.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A tokenizer that processes Java expressions. It knows about words,
+ * quoted strings, comments, and white space. Symbols are <em>not</em>
+ * aggregated into operators.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("reader != null")
+public class JavaTokenizer implements Iterator<JavaTokenizer.Token> {
+ /**
+ * The kind of a token.
+ */
+ public static enum TokenKind {
+ /**
+ * An identifier or keyword.
+ */
+ WORD,
+
+ /**
+ * A quoted literal.
+ */
+ QUOTE,
+
+ /**
+ * A punctuation symbol.
+ */
+ SYMBOL,
+
+ /**
+ * A comment.
+ */
+ COMMENT,
+
+ /**
+ * A sequence of whitespace.
+ */
+ SPACE;
+ }
+
+ /**
+ * A token returned by an instance of JavaTokenizer.
+ */
+ public class Token {
+ public final TokenKind kind;
+ public final String text;
+ public final int offset;
+
+ protected Token(TokenKind kind, String text, int offset) {
+ this.kind = kind;
+ this.text = text;
+ this.offset = offset;
+ }
+ }
+
+ /**
+ * The source of characters to tokenize.
+ */
+ protected PushbackReader reader;
+
+ /**
+ * The next token returned by this tokenizer, or {@code null} if
+ * none (end of file or not read yet).
+ */
+ protected Token nextToken;
+
+ /**
+ * {@code true} if an error was found during tokenization.
+ */
+ protected boolean hasErrors_;
+
+ /**
+ * The error message, if an error has occurred. Can be {@code null}.
+ */
+ protected String errorMessage;
+
+ /**
+ * The number of characters read by this tokenizer.
+ */
+ protected int currentOffset;
+
+ /**
+ * Constructs a new JavaTokenizer reading characters from
+ * {@code reader}.
+ */
+ @Requires("reader != null")
+ public JavaTokenizer(Reader reader) {
+ this.reader = new PushbackReader(reader);
+ nextToken = null;
+ hasErrors_ = false;
+ currentOffset = 0;
+ }
+
+ /**
+ * Reads a character from {@code reader}. In this implementation,
+ * all read accesses to the reader pass through this method.
+ *
+ * @param allowEOF if {@code false}, throws IOException if EOF is
+ * reached
+ */
+ @Ensures({
+ "!allowEOF ? result >= 0 : result >= -1"
+ })
+ protected int readChar(boolean allowEOF) throws IOException {
+ int c = reader.read();
+ if (c != -1) {
+ ++currentOffset;
+ } else {
+ if (!allowEOF) {
+ throw new IOException();
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Pushes back a character into {@code reader}. In this
+ * implementation, all unread accesses to the reader pass through
+ * this method.
+ */
+ protected void unreadChar(int c) throws IOException {
+ if (c == -1) {
+ return;
+ }
+ --currentOffset;
+ reader.unread(c);
+ }
+
+ /**
+ * Reads the next token into {@code nextToken}.
+ *
+ * @return {@code true} if a new token was successfully read
+ */
+ @Requires("nextToken == null")
+ @Ensures("result == (nextToken != null)")
+ protected boolean lex() throws IOException {
+ int startOffset = currentOffset;
+ StringBuilder buffer = new StringBuilder();
+ int c = readChar(true);
+ if (c == -1) {
+ return false;
+ }
+ buffer.append((char) c);
+ switch (c) {
+ case '/':
+ /* Comments. */
+ c = readChar(true);
+ switch (c) {
+ case '/':
+ buffer.append((char) c);
+ do {
+ c = readChar(false);
+ buffer.append((char) c);
+ } while (c != '\n');
+ nextToken = new Token(TokenKind.COMMENT, buffer.toString(),
+ startOffset);
+ break;
+ case '*':
+ buffer.append((char) c);
+ for (;;) {
+ c = readChar(false);
+ buffer.append((char) c);
+ if (c == '*') {
+ c = readChar(false);
+ buffer.append((char) c);
+ if (c == '/') {
+ break;
+ }
+ }
+ }
+ nextToken = new Token(TokenKind.COMMENT, buffer.toString(),
+ startOffset);
+ break;
+ default:
+ unreadChar(c);
+ nextToken = new Token(TokenKind.SYMBOL, buffer.toString(),
+ startOffset);
+ }
+ break;
+
+ case '\'':
+ case '"':
+ /* Quoted strings. */
+ int delim = c;
+ for (;;) {
+ c = readChar(false);
+ buffer.append((char) c);
+ if (c == delim) {
+ break;
+ }
+ if (c == '\\') {
+ buffer.append((char) readChar(false));
+ }
+ }
+ nextToken = new Token(TokenKind.QUOTE, buffer.toString(), startOffset);
+ break;
+
+ default:
+ if (Character.isJavaIdentifierStart(c)) {
+ /* Identifiers. */
+ while ((c = readChar(true)) != -1
+ && Character.isJavaIdentifierPart(c)) {
+ buffer.append((char) c);
+ }
+ unreadChar(c);
+ nextToken = new Token(TokenKind.WORD, buffer.toString(), startOffset);
+ } else if (Character.isWhitespace(c)) {
+ /* White space. */
+ while ((c = readChar(true)) != -1 && Character.isWhitespace(c)) {
+ buffer.append((char) c);
+ }
+ unreadChar(c);
+ nextToken = new Token(TokenKind.SPACE, buffer.toString(),
+ startOffset);
+ } else {
+ /* Symbol. */
+ nextToken = new Token(TokenKind.SYMBOL, buffer.toString(),
+ startOffset);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the next token without consuming it.
+ */
+ public Token getNextToken() {
+ if (nextToken == null) {
+ try {
+ if (!lex()) {
+ throw new NoSuchElementException();
+ }
+ } catch (IOException e) {
+ errorMessage = e.getMessage();
+ hasErrors_ = true;
+ throw new NoSuchElementException();
+ }
+ }
+ return nextToken;
+ }
+
+ public int getCurrentOffset() {
+ return currentOffset;
+ }
+
+ /**
+ * Returns {@code true} if an error has been found.
+ */
+ public boolean hasErrors() {
+ return hasErrors_;
+ }
+
+ @Requires("hasErrors()")
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (nextToken != null) {
+ return true;
+ } else {
+ try {
+ return lex();
+ } catch (IOException e) {
+ errorMessage = e.getMessage();
+ hasErrors_ = true;
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public Token next() {
+ Token token = getNextToken();
+ nextToken = null;
+ return token;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/google/java/contract/core/util/JavaUtils.java b/src/com/google/java/contract/core/util/JavaUtils.java
new file mode 100644
index 0000000..155ba0d
--- /dev/null
+++ b/src/com/google/java/contract/core/util/JavaUtils.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+import java.util.regex.Pattern;
+import javax.tools.JavaFileObject.Kind;
+
+/**
+ * Utility methods related to the Java language.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport(ClassName.class)
+public class JavaUtils {
+ /**
+ * A parse error; thrown by the parsing functions whenever they read
+ * an unexpected token in Java code.
+ */
+ public static class ParseException extends Exception {
+ public ParseException(String msg) {
+ super(msg);
+ }
+
+ public ParseException(Throwable cause) {
+ super(cause);
+ }
+
+ public ParseException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
+ /**
+ * File name extension of contract class files.
+ */
+ public static final String CONTRACTS_EXTENSION = ".contracts";
+
+ /**
+ * File name extension of contracted class files.
+ */
+ public static final String CONTRACTED_EXTENSION = ".class.contracted";
+
+ /**
+ * File name extension of source dependency information files.
+ */
+ public static final String SOURCE_DEPENDENCY_EXTENSION = ".java.d";
+
+ /**
+ * A comment string that marks the beginning of generated code.
+ */
+ public static final String BEGIN_GENERATED_CODE = "/*[*/";
+
+ /**
+ * A comment string that marks the end of generated code.
+ */
+ public static final String END_GENERATED_CODE = "/*]*/";
+
+ /**
+ * A comment delimiter string that marks the beginning of a location
+ * comment, used to track code positions in errors.
+ */
+ public static final String BEGIN_LOCATION_COMMENT = "/*[";
+
+ /**
+ * A comment delimiter string that marks the end of a location
+ * comment, used to track code positions in errors.
+ */
+ public static final String END_LOCATION_COMMENT = "]*/";
+
+ /**
+ * The name of the <em>user-visible</em> result variable.
+ */
+ public static final String RESULT_VARIABLE =
+ "result";
+
+ /**
+ * The name of the <em>user-visible</em> signal variable.
+ */
+ public static final String SIGNAL_VARIABLE =
+ "signal";
+
+ /**
+ * The suffix appended to helper class names.
+ */
+ public static final String HELPER_CLASS_SUFFIX =
+ "$com$google$java$contract$H";
+
+ /**
+ * The prefix added to synthetic member names.
+ */
+ public static final String SYNTHETIC_MEMBER_PREFIX =
+ "com$google$java$contract$S";
+
+ /**
+ * The prefix of all old variable names.
+ */
+ public static final String OLD_VARIABLE_PREFIX =
+ "com$google$java$contract$local$old";
+
+ /**
+ * The prefix of all temporary variables that are assigned the value
+ * of assertion expressions. Their purpose is to syntactically
+ * isolate the user expression in order to get better error
+ * messages, and provide markers for debug line numbering.
+ */
+ public static final String SUCCESS_VARIABLE_PREFIX =
+ "com$google$java$contract$local$success";
+
+ /**
+ * The prefix of all temporary variables for exceptions thrown during
+ * assertion expression evaluation.
+ */
+ public static final String EXCEPTION_VARIABLE_PREFIX =
+ "com$google$java$contract$local$exception";
+
+ /**
+ * The name of a temporary variable that keeps track of failed
+ * contravariant conditions.
+ */
+ public static final String ERROR_VARIABLE =
+ "com$google$java$contract$local$error";
+
+ /**
+ * The name of the parameter holding the object to evaluate the
+ * predicates against, in interface helper contract methods.
+ */
+ public static final String THAT_VARIABLE =
+ "com$google$java$contract$local$that";
+
+ /**
+ * Returns {@code true} if {@code obj} can be cast to class
+ * {@code className}, at run time, using reflection.
+ */
+ @Requires({
+ "obj != null",
+ "className != null"
+ })
+ public static boolean objectIsCastableTo(Object obj, String className) {
+ try {
+ Class<?> clazz = Class.forName(className);
+ return clazz.isAssignableFrom(obj.getClass());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Requires("className != null")
+ public static boolean classExists(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Quotes {@code str} so as to be readily inserted into a Java
+ * "multi-line" comment.
+ */
+ @Requires("str != null")
+ @Ensures("result != null")
+ public static String quoteComment(String str) {
+ return str.replace("/*", "/\\*").replace("*/", "*\\/");
+ }
+
+ /**
+ * Deletes Java comments from {@code code} and returns the resulting
+ * string.
+ */
+ @Requires("code != null")
+ @Ensures("result != null")
+ public static String deleteComments(String code) {
+ StringBuilder buffer = new StringBuilder();
+ JavaTokenizer tokenizer = new JavaTokenizer(new StringReader(code));
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.next();
+ if (token.kind != JavaTokenizer.TokenKind.COMMENT) {
+ buffer.append(token.text);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Deletes generated Java code from {@code code} and returns the
+ * resulting string. Generated code is delimited by matching
+ * {@link #BEGIN_DEBUG_COMMENT} and {@link #END_GENERATED_CODE}
+ * comments.
+ */
+ @Requires("code != null")
+ @Ensures({
+ "result != null",
+ "code.length() == result.length() + generatedCodeLength(code)"
+ })
+ public static String deleteGeneratedCode(String code) {
+ StringBuilder buffer = new StringBuilder();
+ JavaTokenizer tokenizer = new JavaTokenizer(new StringReader(code));
+ boolean ignore = false;
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.next();
+ if (!ignore) {
+ if (token.kind == JavaTokenizer.TokenKind.COMMENT
+ && token.text.equals(BEGIN_GENERATED_CODE)) {
+ ignore = true;
+ } else {
+ buffer.append(token.text);
+ }
+ } else {
+ if (token.kind == JavaTokenizer.TokenKind.COMMENT
+ && token.text.equals(END_GENERATED_CODE)) {
+ ignore = false;
+ }
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the number of characters used in {@code code} for
+ * generated code.
+ *
+ * @see #deleteGeneratedCode(String)
+ */
+ @Requires("code != null")
+ @Ensures("result == code.length() - deleteGeneratedCode(code).length()")
+ public static int generatedCodeLength(String code) {
+ int length = 0;
+ JavaTokenizer tokenizer = new JavaTokenizer(new StringReader(code));
+ boolean ignore = false;
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.next();
+ if (!ignore) {
+ if (token.kind == JavaTokenizer.TokenKind.COMMENT
+ && token.text.equals(BEGIN_GENERATED_CODE)) {
+ ignore = true;
+ length += token.text.length();
+ }
+ } else {
+ if (token.kind == JavaTokenizer.TokenKind.COMMENT
+ && token.text.equals(END_GENERATED_CODE)) {
+ ignore = false;
+ }
+ length += token.text.length();
+ }
+ }
+ return length;
+ }
+
+ /**
+ * Returns {@code code} with all unqualified identifiers remapped
+ * according to {@code map}.
+ */
+ @Requires({
+ "code != null",
+ "map != null"
+ })
+ @Ensures("result != null")
+ public static String renameLocalVariables(String code,
+ Map<String, String> map) {
+ StringBuilder buffer = new StringBuilder();
+ JavaTokenizer tokenizer = new JavaTokenizer(new StringReader(code));
+ boolean qualified = false;
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.next();
+ if (!qualified && token.kind == JavaTokenizer.TokenKind.WORD) {
+ String replacement = map.get(token.text);
+ if (replacement != null) {
+ buffer.append(replacement);
+ } else {
+ buffer.append(token.text);
+ }
+ } else {
+ buffer.append(token.text);
+ }
+ qualified = token.text.equals(".");
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns {@code true} if the next token in {@code tokenizer},
+ * disregarding whitespace, matches {@code text}.
+ */
+ @Requires({
+ "tokenizer != null",
+ "text != null"
+ })
+ public static boolean lookingAt(PushbackTokenizer tokenizer, String text) {
+ JavaTokenizer.Token token1 = tokenizer.peek(0);
+ JavaTokenizer.Token token2 = tokenizer.peek(1);
+ if (token1 == null) {
+ return false;
+ }
+ if (!token1.text.equals(text)) {
+ if (token1.kind != JavaTokenizer.TokenKind.SPACE) {
+ return false;
+ }
+ if (token2 == null) {
+ return false;
+ }
+ if (!token2.text.equals(text)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Advances {@code tokenizer} past the next occurrence of
+ * {@code text} (which must match a full token).
+ */
+ @Requires({
+ "tokenizer != null",
+ "text != null"
+ })
+ public static void skipPast(JavaTokenizer tokenizer, String text) {
+ while (tokenizer.hasNext() && !tokenizer.next().text.equals(text)) {
+ }
+ }
+
+ /**
+ * Parses a Java fully-qualified identifier and positions
+ * {@code tokenizer} on the next token.
+ *
+ * @param tokenizer the tokenizer to read from
+ * @param acceptStar {@code true} if the name can end in {@code .*}
+ * @return the parsed identifier
+ * @throws ParseException if the tokenizer is not at the start of an
+ * identifier
+ */
+ @Requires("tokenizer != null")
+ @Ensures(
+ "!acceptStar ? ClassName.isQualifiedName(result)" +
+ ": ClassName.isQualifiedName(result)" +
+ "|| ClassName.isQualifiedName(result.substring(0, result.length() - 2))"
+ )
+ public static String parseQualifiedName(JavaTokenizer tokenizer,
+ boolean acceptStar)
+ throws ParseException {
+ StringBuilder buffer = new StringBuilder();
+ boolean expectWord = true;
+ loop:
+ while (tokenizer.hasNext()) {
+ JavaTokenizer.Token token = tokenizer.getNextToken();
+ switch (token.kind) {
+ case WORD:
+ if (expectWord) {
+ buffer.append(token.text);
+ expectWord = false;
+ } else {
+ break loop;
+ }
+ break;
+ case SYMBOL:
+ switch (token.text.charAt(0)) {
+ case '.':
+ if (!expectWord) {
+ buffer.append(".");
+ expectWord = true;
+ break;
+ } else {
+ break loop;
+ }
+ case '*':
+ if (acceptStar && expectWord) {
+ buffer.append("*");
+ tokenizer.next();
+ }
+ break loop;
+ default:
+ break loop;
+ }
+ break;
+ case SPACE:
+ break;
+ default:
+ break loop;
+ }
+ tokenizer.next();
+ }
+ if (buffer.length() == 0) {
+ throw new ParseException("next token is not part of an identifier");
+ }
+ return buffer.toString();
+ }
+
+ public static String parseQualifiedName(JavaTokenizer tokenizer)
+ throws ParseException {
+ return parseQualifiedName(tokenizer, false);
+ }
+
+ /**
+ * Returns a {@link URLClassLoader} that searches {@code path} and
+ * delegates to {@code parent}.
+ */
+ @Requires("path != null")
+ public static URLClassLoader getLoaderForPath(String path,
+ ClassLoader parent) {
+ String[] parts = path.split(Pattern.quote(File.pathSeparator));
+ URL[] urls = new URL[parts.length];
+ for (int i = 0; i < parts.length; ++i) {
+ try {
+ urls[i] = new File(parts[i]).toURI().toURL();
+ } catch (MalformedURLException e) {
+ /* Ignore erroneous paths. */
+ }
+ }
+ if (parent == null) {
+ return new URLClassLoader(urls);
+ } else {
+ return new URLClassLoader(urls, parent);
+ }
+ }
+
+ @Requires("path != null")
+ public static URLClassLoader getLoaderForPath(String path) {
+ return getLoaderForPath(path, null);
+ }
+
+ /**
+ * Reads the class file of the specified class, as a stream. The
+ * class file is searched in the following places, in order:
+ *
+ * <ol>
+ * <li>The resources of the current class loader, if any.
+ * <li>The resources of the system class loader.
+ * </ol>
+ *
+ * @param loader the class loader used to load resources
+ * @param className the class name, in binary format
+ * @return the content of the contract class file, as a stream, or
+ * {@code null} if none was found
+ */
+ @Requires("ClassName.isBinaryName(className)")
+ public static InputStream getClassInputStream(ClassLoader loader,
+ String className) {
+ String fileName = className + Kind.CLASS.extension;
+ URL url;
+
+ if (loader != null) {
+ url = loader.getResource(fileName);
+ if (url == null) {
+ return null;
+ } else {
+ DebugUtils.info("loader", "found " + url);
+ return loader.getResourceAsStream(fileName);
+ }
+ } else {
+ url = ClassLoader.getSystemResource(fileName);
+ if (url == null) {
+ return null;
+ } else {
+ DebugUtils.info("loader", "found " + url);
+ return ClassLoader.getSystemResourceAsStream(fileName);
+ }
+ }
+ }
+
+ /**
+ * Reads the contract class file of the specified class, as a
+ * stream. The contract class file is searched in the following
+ * places, in order:
+ *
+ * <ol>
+ * <li>The resources of the current class loader, if any.
+ * <li>The resources of the system class loader.
+ * </ol>
+ *
+ * @param loader the class loader used to load resources
+ * @param className the class name, in binary format
+ * @return the content of the contract class file, as a stream, or
+ * {@code null} if none was found
+ */
+ @Requires("ClassName.isBinaryName(className)")
+ public static InputStream getContractClassInputStream(ClassLoader loader,
+ String className,
+ boolean loadHelper) {
+ String fileName = className + CONTRACTS_EXTENSION;
+ String helperFileName = className + HELPER_CLASS_SUFFIX
+ + Kind.CLASS.extension;
+
+ URL url;
+
+ if (loader != null) {
+ url = loader.getResource(helperFileName);
+ if (url != null && loadHelper) {
+ DebugUtils.info("loader", "found " + url);
+ return loader.getResourceAsStream(helperFileName);
+ }
+
+ url = loader.getResource(fileName);
+ if (url != null) {
+ DebugUtils.info("loader", "found " + url);
+ return loader.getResourceAsStream(fileName);
+ }
+
+ return null;
+ } else {
+ url = ClassLoader.getSystemResource(helperFileName);
+ if (url != null && loadHelper) {
+ DebugUtils.info("loader", "found " + url);
+ return ClassLoader.getSystemResourceAsStream(helperFileName);
+ }
+
+ url = ClassLoader.getSystemResource(fileName);
+ if (url != null) {
+ DebugUtils.info("loader", "found " + url);
+ return ClassLoader.getSystemResourceAsStream(fileName);
+ }
+
+ return null;
+ }
+ }
+
+ public static InputStream getContractClassInputStream(ClassLoader loader,
+ String className) {
+ return getContractClassInputStream(loader, className, false);
+ }
+
+ @Requires("className != null")
+ public static boolean resourceExists(ClassLoader loader, String className) {
+ if (loader != null) {
+ return loader.getResource(className) != null;
+ } else {
+ return ClassLoader.getSystemResource(className) != null;
+ }
+ }
+}
diff --git a/src/com/google/java/contract/core/util/LineNumberingTokenizer.java b/src/com/google/java/contract/core/util/LineNumberingTokenizer.java
new file mode 100644
index 0000000..e727c0e
--- /dev/null
+++ b/src/com/google/java/contract/core/util/LineNumberingTokenizer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A tokenizer that keeps track of the current line number.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant("currentLineNumber >= 1")
+public class LineNumberingTokenizer extends JavaTokenizer {
+ /**
+ * The current line number, after the last processed token.
+ */
+ protected long currentLineNumber;
+
+ /**
+ * Constructs a new LineNumberingTokenizer reading characters from
+ * {@code reader}.
+ */
+ @Requires("reader != null")
+ public LineNumberingTokenizer(Reader reader) {
+ super(reader);
+ currentLineNumber = 1;
+ }
+
+ @Override
+ protected boolean lex() throws IOException {
+ if (!super.lex()) {
+ return false;
+ }
+ if (nextToken.kind == TokenKind.SPACE
+ || nextToken.kind == TokenKind.COMMENT) {
+ int lastIndex = -1;
+ for (;;) {
+ lastIndex = nextToken.text.indexOf('\n', lastIndex + 1);
+ if (lastIndex == -1) {
+ break;
+ }
+ ++currentLineNumber;
+ }
+ }
+ return true;
+ }
+
+ @Ensures("result >= 1")
+ public long getCurrentLineNumber() {
+ return currentLineNumber;
+ }
+}
diff --git a/src/com/google/java/contract/core/util/PatternMap.java b/src/com/google/java/contract/core/util/PatternMap.java
new file mode 100644
index 0000000..177f610
--- /dev/null
+++ b/src/com/google/java/contract/core/util/PatternMap.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.core.model.ClassName;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+
+import java.util.TreeMap;
+
+/**
+ * A map that stores associations between patterns and rules. Patterns
+ * can overlap: the last registered pattern overrides previous ones.
+ *
+ * <p>Patterns are semi-qualified names (nested classes have their
+ * names flattened), optionally followed by {@code .*}. A normal
+ * pattern matches itself exactly. A star pattern matches any class
+ * whose name begins with the pattern minus the terminating
+ * {@code .*}.
+ *
+ * <p>The characters {@code .} and {@code /} can be used
+ * interchangeably in patterns and names.
+ *
+ * <p><i>Implementation note:</i> This implementation uses a ternary
+ * search tree based on {@link TreeMap}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @param <R> the type of a rule
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant("root != null")
+public class PatternMap<R> {
+ /**
+ * A node in the internal ternary search tree.
+ */
+ @Invariant({
+ "children != null",
+ "Iterables.all(children.keySet(), ClassName.isSimpleName())",
+ "!children.values().contains(null)"
+ })
+ protected class TernaryNode {
+ protected TreeMap<String, TernaryNode> children;
+ protected R rule;
+ protected boolean exact;
+
+ protected TernaryNode(R rule, boolean exact) {
+ children = new TreeMap<String, TernaryNode>();
+ this.rule = rule;
+ this.exact = exact;
+ }
+ }
+
+ protected TernaryNode root;
+
+ public PatternMap() {
+ root = new TernaryNode(null, false);
+ }
+
+ /**
+ * Returns the rule associated with {@code pattern}.
+ */
+ @Requires("isValidPattern(pattern)")
+ public R get(String pattern) {
+ String canon = pattern.replace('/', '.');
+ boolean exact = !canon.endsWith(".*");
+ if (!exact) {
+ canon = canon.substring(0, canon.length() - 2);
+ }
+
+ String[] parts = canon.split("\\.");
+ TernaryNode current = root;
+ R best = null;
+ for (int i = 0; i < parts.length; ++i) {
+ TernaryNode next = current.children.get(parts[i]);
+ if (next == null) {
+ break;
+ }
+ if (next.rule != null) {
+ if (next.exact) {
+ if (exact && i == parts.length - 1) {
+ best = next.rule;
+ }
+ } else {
+ best = next.rule;
+ }
+ }
+ current = next;
+ }
+ return best;
+ }
+
+ /**
+ * Returns {@code true} if {@code pattern} has another pattern
+ * overriding it, that is if there exists some name that is matched
+ * by {@code pattern} and is associated with a different rule than
+ * the one associated with {@code pattern}.
+ *
+ * <p>For example, following the calls {@code put("a.*", r1)} and
+ * {@code put("a.b.*", r2)}, {@code isOverriden("a.*")} will return
+ * {@code true} if {@code !r1.equals(r2)}.
+ *
+ * <p>This method always returns {@code false} if {@code pattern} is
+ * exact.
+ */
+ @Requires("isValidPattern(pattern)")
+ public boolean isOverriden(String pattern) {
+ String canon = pattern.replace('/', '.');
+ boolean exact = !canon.endsWith(".*");
+ if (exact) {
+ return false;
+ }
+ canon = canon.substring(0, canon.length() - 2);
+
+ String[] parts = canon.split("\\.");
+ TernaryNode current = root;
+ for (int i = 0; i < parts.length; ++i) {
+ TernaryNode next = current.children.get(parts[i]);
+ if (next == null) {
+ return false;
+ }
+ current = next;
+ }
+ return !current.children.isEmpty();
+ }
+
+ /**
+ * Associates {@code rule} with {@code pattern}.
+ */
+ @Requires({
+ "isValidPattern(pattern)",
+ "rule != null"
+ })
+ @Ensures({
+ "rule.equals(get(pattern))",
+ "!isOverriden(pattern)"
+ })
+ public void put(String pattern, R rule) {
+ String canon = pattern.replace('/', '.');
+ boolean exact = !canon.endsWith(".*");
+ if (!exact) {
+ canon = canon.substring(0, canon.length() - 2);
+ }
+
+ String[] parts = canon.split("\\.");
+
+ TernaryNode current = root;
+ TernaryNode parent = root;
+ int parentIndex = -1;
+ for (int i = 0; i < parts.length; ++i) {
+ TernaryNode next = current.children.get(parts[i]);
+ if (next == null) {
+ next = new TernaryNode(null, false);
+ current.children.put(parts[i], next);
+ }
+ if (next.rule != null && !next.exact) {
+ parent = next;
+ parentIndex = i;
+ }
+ current = next;
+ }
+
+ if (!exact) {
+ /* Override patterns beginning with this one. */
+ current.children.clear();
+ }
+
+ if (!rule.equals(parent.rule)) {
+ current.rule = rule;
+ current.exact = exact;
+ } else {
+ /*
+ * The new rule we are inserting is already specified through
+ * inheritance. Remove spurious nodes. This ensures that any
+ * overriding branch is meaningful; that is, it specifies a rule
+ * value different from the inherited one.
+ */
+ current = parent;
+ for (int i = parentIndex + 1; i < parts.length; ++i) {
+ TernaryNode next = current.children.get(parts[i]);
+ if (next.rule == null) {
+ current.children.remove(parts[i]);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if {@code pattern} is a valid pattern.
+ */
+ public static boolean isValidPattern(String pattern) {
+ if (pattern == null) {
+ return false;
+ }
+ String canon = pattern.replace('/', '.');
+ return ClassName.isQualifiedName(canon)
+ || ClassName.isStarQualifiedName(canon);
+ }
+}
diff --git a/src/com/google/java/contract/core/util/PushbackTokenizer.java b/src/com/google/java/contract/core/util/PushbackTokenizer.java
new file mode 100644
index 0000000..3ae2edf
--- /dev/null
+++ b/src/com/google/java/contract/core/util/PushbackTokenizer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * A tokenizer that supports look-ahead through a push-back interface.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({
+ "queue != null",
+ "!queue.contains(null)"
+})
+public class PushbackTokenizer extends JavaTokenizer {
+ /**
+ * Pushback queue.
+ */
+ protected Deque<Token> queue;
+
+ /**
+ * Constructs a new PushbackTokenizer reading characters from
+ * {@code reader}.
+ */
+ @Requires("reader != null")
+ public PushbackTokenizer(Reader reader) {
+ super(reader);
+ queue = new ArrayDeque<Token>();
+ }
+
+ @Requires("token != null")
+ @Ensures("token == getNextToken()")
+ public void pushback(Token token) {
+ queue.push(token);
+ }
+
+ @Override
+ public Token getNextToken() {
+ Token token = queue.peek();
+ if (token != null) {
+ return token;
+ } else {
+ return super.getNextToken();
+ }
+ }
+
+ @Override
+ public int getCurrentOffset() {
+ Token token = queue.peek();
+ if (token != null) {
+ return token.offset;
+ } else {
+ return super.getCurrentOffset();
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!queue.isEmpty()) {
+ return true;
+ } else {
+ return super.hasNext();
+ }
+ }
+
+ @Override
+ public Token next() {
+ Token token = queue.poll();
+ if (token != null) {
+ return token;
+ } else {
+ return super.next();
+ }
+ }
+
+ /**
+ * Returns the next {@code lookahead}-th token, or {@code null} if
+ * there is none. Look-ahead tokens are counted from 0.
+ */
+ @Requires("lookahead >= 0")
+ public Token peek(int lookahead) {
+ ArrayDeque<Token> tmp = new ArrayDeque<Token>();
+ for (int i = 0; i <= lookahead; ++i) {
+ if (hasNext()) {
+ tmp.push(next());
+ }
+ }
+ Token token = tmp.peek();
+ while (!tmp.isEmpty()) {
+ pushback(tmp.pop());
+ }
+ return token;
+ }
+}
diff --git a/src/com/google/java/contract/core/util/SyntheticJavaFile.java b/src/com/google/java/contract/core/util/SyntheticJavaFile.java
new file mode 100644
index 0000000..08e8bd0
--- /dev/null
+++ b/src/com/google/java/contract/core/util/SyntheticJavaFile.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.core.util;
+
+import com.google.java.contract.AllowUnusedImport;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+import com.google.java.contract.util.Iterables;
+import com.google.java.contract.util.Predicates;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+import javax.tools.SimpleJavaFileObject;
+
+/**
+ * An in-memory Java source file, with mapping between contract model
+ * elements and line numbers, representing a contract source.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at AllowUnusedImport({ Iterables.class, Predicates.class })
+ at Invariant({
+ "content != null",
+ "lineNumberMap == null " +
+ "|| Iterables.all(lineNumberMap.keySet(), Predicates.between(1L, null))"
+})
+public class SyntheticJavaFile extends SimpleJavaFileObject {
+ protected byte[] content;
+ protected Map<Long, ?> lineNumberMap;
+
+ /**
+ * Constructs a new SyntheticJavaFile. This constructor wraps its
+ * argument into a SyntheticJavaFile; the objects are <em>not</em>
+ * copied.
+ *
+ * @param name the class name of the file, in qualified format
+ * @param content the content of the file
+ * @param lineNumberMap the line number mapping
+ */
+ @Requires({
+ "name != null",
+ "content != null"
+ })
+ public SyntheticJavaFile(String name, byte[] content,
+ Map<Long, ?> lineNumberMap) {
+ super(Elements.getUriForClass(name, Kind.SOURCE), Kind.SOURCE);
+ this.content = content;
+ this.lineNumberMap = lineNumberMap;
+ }
+
+ @Override
+ public InputStream openInputStream() {
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return new String(content);
+ }
+
+ @Override
+ public boolean delete() {
+ content = null;
+ return true;
+ }
+
+ /**
+ * Returns the source information object associated with the
+ * specified line number.
+ */
+ public Object getSourceInfo(long lineNumber) {
+ return lineNumberMap == null ? null : lineNumberMap.get(lineNumber);
+ }
+}
diff --git a/src/com/google/java/contract/util/Iterables.java b/src/com/google/java/contract/util/Iterables.java
new file mode 100644
index 0000000..d590bb7
--- /dev/null
+++ b/src/com/google/java/contract/util/Iterables.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.util;
+
+/**
+ * Utilities for iterables.
+ *
+ * <p>These methods are intentionally name-compatible with
+ * {@link com.google.common.collect.Iterables}, so as to make it easy
+ * to switch between them.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public final class Iterables {
+ private Iterables() {
+ }
+
+ /**
+ * Applies {@code all(p)} to {@code it}.
+ *
+ * <p><b>Warning:</b> this method accepts a {@code null} iterable
+ * object and returns {@code false}. The similarly-named method in
+ * {@link com.google.common.collect.Iterables} does <em>not</em> take
+ * {@code null} as input.
+ */
+ public static <T> boolean all(Iterable<T> it, Predicate<? super T> p) {
+ if (it == null) {
+ return false;
+ }
+ for (T elem : it) {
+ if (!p.apply(elem)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Applies {@code any(p)} to {@code it}.
+ *
+ * <p><b>Warning:</b> this method accepts a {@code null} iterable
+ * object and returns {@code false}. The similarly-named method in
+ * {@link com.google.common.collect.Iterables} does <em>not</em> take
+ * {@code null} as input.
+ */
+ public static <T> boolean any(Iterable<T> it, Predicate<? super T> p) {
+ if (it == null) {
+ return false;
+ }
+ for (T elem : it) {
+ if (p.apply(elem)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/google/java/contract/util/Objects.java b/src/com/google/java/contract/util/Objects.java
new file mode 100644
index 0000000..61c61e4
--- /dev/null
+++ b/src/com/google/java/contract/util/Objects.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.util;
+
+/**
+ * Utilities for objects.
+ *
+ * <p>These methods are intentionally name-compatible with
+ * {@link com.google.common.base.Objects}, so as to make it easy
+ * to switch between them.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public final class Objects {
+ private Objects() {
+ }
+
+ /**
+ * Returns {@code true} if both its arguments are {@code null} or
+ * they compare equal.
+ */
+ public static <T> boolean equal(T a, T b) {
+ return a == null && b == null || a.equals(b);
+ }
+}
diff --git a/src/com/google/java/contract/util/Predicate.java b/src/com/google/java/contract/util/Predicate.java
new file mode 100644
index 0000000..335f564
--- /dev/null
+++ b/src/com/google/java/contract/util/Predicate.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.util;
+
+/**
+ * A function-object that returns a boolean.
+ *
+ * <p>This interface is intentionally name-compatible with
+ * {@link com.google.common.base.Predicate}, so as to make it easy to
+ * switch between them through static imports.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @param <T> the type of input to this predicate
+ */
+public interface Predicate<T> {
+ /**
+ * Applies this predicate to {@code obj}.
+ */
+ public boolean apply(T obj);
+}
diff --git a/src/com/google/java/contract/util/Predicates.java b/src/com/google/java/contract/util/Predicates.java
new file mode 100644
index 0000000..c93522b
--- /dev/null
+++ b/src/com/google/java/contract/util/Predicates.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for predicates.
+ *
+ * <p>These methods are intentionally name-compatible with
+ * {@link com.google.common.base.Predicates}, so as to make it easy
+ * to switch between them.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public final class Predicates {
+ private static final Predicate<Object> TRUE = new Predicate<Object>() {
+ @Override
+ public boolean apply(Object obj) {
+ return true;
+ }
+ };
+
+ private static final Predicate<Object> FALSE = new Predicate<Object>() {
+ @Override
+ public boolean apply(Object obj) {
+ return false;
+ }
+ };
+
+ private static final Predicate<Object> IS_NULL = new Predicate<Object>() {
+ @Override
+ public boolean apply(Object obj) {
+ return obj == null;
+ }
+ };
+
+ private static final Predicate<Object> NON_NULL = new Predicate<Object>() {
+ @Override
+ public boolean apply(Object obj) {
+ return obj != null;
+ }
+ };
+
+ private Predicates() {
+ }
+
+ /**
+ * Narrows {@code p} to apply to type {@code T}.
+ */
+ @SuppressWarnings("unchecked")
+ public static <S, T extends S> Predicate<T> narrow(Predicate<S> p) {
+ return (Predicate<T>) p;
+ }
+
+ /**
+ * Returns the constant predicate that always returns {@code b}.
+ */
+ public static <T> Predicate<T> constant(final boolean b) {
+ return b
+ ? Predicates.<Object, T>narrow(TRUE)
+ : Predicates.<Object, T>narrow(FALSE);
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is equal to {@code obj}.
+ */
+ public static <T> Predicate<T> equalTo(final T obj) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T o) {
+ return Objects.equal(o, obj);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is {@code null}.
+ */
+ public static <T> Predicate<T> isNull() {
+ return Predicates.<Object, T>narrow(IS_NULL);
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is not {@code null}.
+ */
+ public static <T> Predicate<T> nonNull() {
+ return Predicates.<Object, T>narrow(NON_NULL);
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is between {@code low} (inclusive) and {@code high}
+ * (exclusive).
+ */
+ public static <T extends Comparable<T>> Predicate<T> between(
+ final T low, final T high) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ return obj != null
+ && (low == null || obj.compareTo(low) >= 0)
+ && (high == null || obj.compareTo(high) < 0);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is a member of {@code it}.
+ */
+ public static <T> Predicate<T> in(final Iterable<T> it) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ for (T elem : it) {
+ if (Objects.equal(elem, obj)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument is a member of {@code c}.
+ */
+ public static <T> Predicate<T> in(final Collection<T> c) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ return c.contains(obj);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument does not satisfy {@code p}.
+ */
+ public static <T> Predicate<T> not(final Predicate<? super T> p) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ return !p.apply(obj);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument satisfies all predicates {@code ps}.
+ */
+ public static <T> Predicate<T> and(final Predicate<? super T>... ps) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ for (Predicate<? super T> p : ps) {
+ if (!p.apply(obj)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if its
+ * argument satisfies any of the predicates {@code ps}.
+ */
+ public static <T> Predicate<T> or(final Predicate<? super T>... ps) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T obj) {
+ for (Predicate<? super T> p : ps) {
+ if (p.apply(obj)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if all the
+ * elements of its argument satisfies {@code p}.
+ */
+ public static <T> Predicate<Iterable<T>> all(
+ final Predicate<? super T> p) {
+ return new Predicate<Iterable<T>>() {
+ @Override
+ public boolean apply(Iterable<T> obj) {
+ return Iterables.all(obj, p);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if any element
+ * of its argument satisfies {@code p}.
+ */
+ public static <T> Predicate<Iterable<T>> any(
+ final Predicate<? super T> p) {
+ return new Predicate<Iterable<T>>() {
+ @Override
+ public boolean apply(Iterable<T> obj) {
+ return Iterables.any(obj, p);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if the entry
+ * set of its argument satisfies {@code p}.
+ */
+ public static <K, V> Predicate<Map<K, V>> forEntries(
+ final Predicate<? super Set<Map.Entry<K, V>>> p) {
+ return new Predicate<Map<K, V>>() {
+ @Override
+ public boolean apply(Map<K, V> obj) {
+ return p.apply(obj.entrySet());
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if the key set
+ * of its argument satisfies {@code p}.
+ */
+ public static <K, V> Predicate<Map<K, V>> forKeys(
+ final Predicate<? super Set<K>> p) {
+ return new Predicate<Map<K, V>>() {
+ @Override
+ public boolean apply(Map<K, V> obj) {
+ return p.apply(obj.keySet());
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if the value
+ * collection of its argument satisfies {@code p}.
+ */
+ public static <K, V> Predicate<Map<K, V>> forValues(
+ final Predicate<? super Collection<V>> p) {
+ return new Predicate<Map<K, V>>() {
+ @Override
+ public boolean apply(Map<K, V> obj) {
+ return p.apply(obj.values());
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that applies {@code any(p)} to the entries of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> anyEntry(
+ Predicate<? super Map.Entry<K, V>> p) {
+ return forEntries(Predicates.<Map.Entry<K, V>>any(p));
+ }
+
+ /**
+ * Returns a predicate that applies {@code any(p)} to the keys of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> anyKey(Predicate<? super K> p) {
+ return forKeys(Predicates.<K>any(p));
+ }
+
+ /**
+ * Returns a predicate that applies {@code any(p)} to the values of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> anyValue(Predicate<? super V> p) {
+ return forValues(Predicates.<V>any(p));
+ }
+
+ /**
+ * Returns a predicate that applies {@code all(p)} to the entries of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> allEntries(
+ Predicate<? super Map.Entry<K, V>> p) {
+ return forEntries(Predicates.<Map.Entry<K, V>>all(p));
+ }
+
+ /**
+ * Returns a predicate that applies {@code all(p)} to the keys of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> allKeys(Predicate<? super K> p) {
+ return forKeys(Predicates.<K>all(p));
+ }
+
+ /**
+ * Returns a predicate that applies {@code all(p)} to the values of
+ * its argument.
+ */
+ public static <K, V> Predicate<Map<K, V>> allValues(Predicate<? super V> p) {
+ return forValues(Predicates.<V>all(p));
+ }
+}
diff --git a/test/com/google/java/contract/examples/ArrayListStack.java b/test/com/google/java/contract/examples/ArrayListStack.java
new file mode 100644
index 0000000..67af6bc
--- /dev/null
+++ b/test/com/google/java/contract/examples/ArrayListStack.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.examples;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+
+import java.util.ArrayList;
+
+/**
+ * An implementation of the {@link Stack} example interface using an
+ * {@link ArrayList}, demonstrating the use of contracts.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @param <T> the type of an element
+ */
+ at Invariant("elements != null")
+public class ArrayListStack<T> implements Stack<T> {
+ protected ArrayList<T> elements;
+
+ public ArrayListStack() {
+ elements = new ArrayList<T>();
+ }
+
+ @Override
+ public int size() {
+ return elements.size();
+ }
+
+ @Override
+ public T peek() {
+ return elements.get(elements.size() - 1);
+ }
+
+ @Override
+ public T pop() {
+ return elements.remove(elements.size() - 1);
+ }
+
+ @Override
+ @Ensures("elements.contains(old (obj))")
+ public void push(T obj) {
+ elements.add(obj);
+ }
+}
diff --git a/test/com/google/java/contract/examples/Stack.java b/test/com/google/java/contract/examples/Stack.java
new file mode 100644
index 0000000..4e2f2ce
--- /dev/null
+++ b/test/com/google/java/contract/examples/Stack.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.examples;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+/**
+ * A simple stack interface demonstrating the use of contracts. This
+ * interface does not extend java.lang.Collection as we are lazy and
+ * want a small example. Besides, Collection itself is not contracted,
+ * and some of the contracts would more logically fit there than in
+ * this interface.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ * @param <T> the type of an element
+ */
+ at Invariant("size() >= 0")
+public interface Stack<T> {
+ /**
+ * Returns the number of elements in this stack.
+ */
+ public int size();
+
+ /**
+ * Returns the topmost element of this stack without removing it.
+ */
+ @Requires("size() >= 1")
+ public T peek();
+
+ /**
+ * Pops the topmost element off this stack.
+ */
+ @Requires("size() >= 1")
+ @Ensures({
+ "size() == old (size()) - 1",
+ "result == old (peek())"
+ })
+ public T pop();
+
+ /**
+ * Pushes an element onto the stack.
+ */
+ @Ensures({
+ "size() == old (size()) + 1",
+ "peek() == old (obj)"
+ })
+ public void push(T obj);
+}
diff --git a/test/com/google/java/contract/tests/Cofoja.java b/test/com/google/java/contract/tests/Cofoja.java
new file mode 100644
index 0000000..bd0a6b7
--- /dev/null
+++ b/test/com/google/java/contract/tests/Cofoja.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.ContractEnvironment;
+import com.google.java.contract.ContractEnvironmentConfigurator;
+
+/**
+ * Contracts for Java configuration and initialization.
+ */
+public class Cofoja implements ContractEnvironmentConfigurator {
+ static ContractEnvironment contractEnv;
+
+ @Override
+ public void configure(ContractEnvironment env) {
+ contractEnv = env;
+ }
+}
diff --git a/test/com/google/java/contract/tests/ConstantContracts.java b/test/com/google/java/contract/tests/ConstantContracts.java
new file mode 100644
index 0000000..291c759
--- /dev/null
+++ b/test/com/google/java/contract/tests/ConstantContracts.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Requires;
+
+/**
+ * This class exposes some constant contracts.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+class ConstantContracts {
+ @Requires("false || false")
+ public void preFailure() {
+ }
+
+ @Ensures("false")
+ public void postFailure() {
+ }
+
+ @Requires("true")
+ @Ensures("false")
+ public void postFailure1() {
+ }
+
+ @Requires("true")
+ @Ensures({ "false && false || false", "true", "true && false" })
+ public void postFailure2() {
+ }
+
+ @Requires("true")
+ public void preSuccess() {
+ }
+
+ @Ensures("true")
+ public void postSuccess() {
+ }
+
+ @Requires("true")
+ @Ensures("true")
+ public void postSuccess1() {
+ }
+
+ @Requires("true")
+ @Ensures("true")
+ public void postSuccess2() {
+ }
+
+ @Requires({ "true", "true" })
+ @Ensures("true")
+ public void postSuccess3() {
+ }
+}
diff --git a/test/com/google/java/contract/tests/ConstantContractsTest.java b/test/com/google/java/contract/tests/ConstantContractsTest.java
new file mode 100644
index 0000000..060afcd
--- /dev/null
+++ b/test/com/google/java/contract/tests/ConstantContractsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts made of constant expressions.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class ConstantContractsTest extends TestCase {
+ ConstantContracts sample;
+
+ @Override
+ protected void setUp() {
+ sample = new ConstantContracts();
+ }
+
+ public void testPreFailsAndHasCorrectMessage() {
+ try {
+ sample.preFailure();
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[false || false]", expected.getMessages().toString());
+ }
+ }
+
+ public void testPostFailure() {
+ try {
+ sample.postFailure();
+ fail();
+ } catch (PostconditionError expected) {
+ /* Expected. */
+ }
+ }
+
+ public void testPostFailure1() {
+ try {
+ sample.postFailure1();
+ fail();
+ } catch (PostconditionError expected) {
+ /* Expected. */
+ }
+ }
+
+ public void testPostFailure2() {
+ try {
+ sample.postFailure2();
+ fail();
+ } catch (PostconditionError expected) {
+ /* Expected. */
+ }
+ }
+
+ public void testPreSuccess() {
+ sample.preSuccess();
+ }
+
+ public void testPostSuccess() {
+ sample.postSuccess();
+ }
+
+ public void testPostSuccess1() {
+ sample.postSuccess1();
+ }
+
+ public void testPostSuccess2() {
+ sample.postSuccess2();
+ }
+
+ public void testPostSuccess3() {
+ sample.postSuccess3();
+ }
+}
diff --git a/test/com/google/java/contract/tests/ConstructorTest.java b/test/com/google/java/contract/tests/ConstructorTest.java
new file mode 100644
index 0000000..39dd1ef
--- /dev/null
+++ b/test/com/google/java/contract/tests/ConstructorTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+import junit.framework.TestCase;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * Tests contracts that apply to constructors.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class ConstructorTest extends TestCase {
+ @Invariant("a >= 0")
+ private static class A {
+ protected int a;
+
+ public A(int x) {
+ /* Bogus for x < 0. */
+ a = x;
+ }
+ }
+
+ private static class B extends A {
+ public B(int x) {
+ /* Bogus for x < 0. */
+ super(x);
+ }
+ }
+
+ private static class C extends A {
+ @Requires("x >= 1")
+ public C(int x) {
+ super(x);
+ }
+ }
+
+ /* double super argument. */
+ private static class D {
+ @Requires("x >= 0")
+ public D(double x) {
+ }
+ }
+
+ private static class Dx extends D {
+ public Dx(double x) {
+ super(x);
+ }
+ }
+
+ /* char super argument. */
+ private static class Ch {
+ @Requires("x != '\0'")
+ public Ch(char x) {
+ }
+ }
+
+ private static class Chx extends Ch {
+ public Chx(char x) {
+ super(x);
+ }
+ }
+
+ private static class In extends FileInputStream {
+ @Requires("!name.equals(\"/dev/null\")")
+ public In(String name) throws FileNotFoundException {
+ super(name);
+ }
+ }
+
+ public void testA() {
+ A b = new A(84);
+ }
+
+ public void testABogus() {
+ try {
+ A b = new A(-64390);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testB() {
+ B b = new B(1846);
+ }
+
+ public void testBBogus() {
+ try {
+ B b = new B(-3829);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testCInvalidArgument() {
+ try {
+ C b = new C(0);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x >= 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testDx() {
+ try {
+ Dx dx = new Dx(-3.0);
+ } catch (PreconditionError expected) {
+ assertEquals("[x >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testChx() {
+ try {
+ Chx chx = new Chx('\0');
+ } catch (PreconditionError expected) {
+ assertEquals("[x != '\0']", expected.getMessages().toString());
+ }
+ }
+
+ public void testIn() {
+ try {
+ In in = new In("XXX");
+ fail();
+ } catch (FileNotFoundException expected) {
+ /* File 'XXX' does not exist. */
+ }
+ }
+
+ public void testInInvalidArgument() throws FileNotFoundException {
+ try {
+ In in = new In("/dev/null");
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[!name.equals(\"/dev/null\")]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/ContractedAnnotationTest.java b/test/com/google/java/contract/tests/ContractedAnnotationTest.java
new file mode 100644
index 0000000..caf631a
--- /dev/null
+++ b/test/com/google/java/contract/tests/ContractedAnnotationTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+
+/**
+ * Tests that the annotation processor handles correctly contracts applied
+ * to annotation types, ignoring them, but maintaining annotation's
+ * informations.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class ContractedAnnotationTest extends TestCase {
+ /**
+ * If this annotation verifies it's contracts, a failure should be produced.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Invariant("false")
+ private static @interface MyAnnotation {
+ @Requires("false")
+ String value() default "none";
+ }
+
+ /**
+ * Tester class, uses the annotation defined above.
+ */
+ @MyAnnotation("X")
+ private static class UsesAnnotations {
+ @MyAnnotation
+ public void m1() {}
+ @MyAnnotation
+ public void m2() {}
+ @MyAnnotation
+ public void m3() {}
+ @MyAnnotation
+ public void m4() {}
+
+ @Requires("anno.value().equals(\"error\")")
+ public void trickyContract(MyAnnotation anno) {}
+
+ @Requires("anno.value().equals(\"none\")")
+ public void trickyContractDefaultValue(MyAnnotation anno) {}
+ }
+
+ UsesAnnotations uses;
+
+ public void testInnerAnnotation() {
+ uses = new UsesAnnotations();
+ }
+
+ /*
+ * Test that annotations effectively do not trigger contracts.
+ */
+ public void testEmptyAnnotationContracts() {
+ Class<UsesAnnotations> ua = UsesAnnotations.class;
+ MyAnnotation anno = ua.getAnnotation(MyAnnotation.class);
+ assertEquals("X", anno.value());
+
+ /* All methods are annotated with the default value. */
+ for (Method m : ua.getMethods()) {
+ anno = m.getAnnotation(MyAnnotation.class);
+ if (anno != null) {
+ assertEquals("none", anno.value());
+ }
+ }
+
+ }
+
+ public void testAnnotationOnContract() throws SecurityException,
+ NoSuchMethodException {
+ uses = new UsesAnnotations();
+ Class<UsesAnnotations> ua = UsesAnnotations.class;
+ MyAnnotation xanno = ua.getAnnotation(MyAnnotation.class);
+ try {
+ uses.trickyContract(xanno);
+ fail();
+ } catch (PreconditionError e) {
+ /* Should catch this. */
+ }
+
+ Method m1 = ua.getMethod("m1", (Class<?>[]) null);
+ MyAnnotation danno = m1.getAnnotation(MyAnnotation.class);
+ try {
+ uses.trickyContractDefaultValue(danno);
+ } catch (PreconditionError e) {
+ fail();
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/ContractedClass.java b/test/com/google/java/contract/tests/ContractedClass.java
new file mode 100644
index 0000000..9468d76
--- /dev/null
+++ b/test/com/google/java/contract/tests/ContractedClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+
+/**
+ * A dumb contracted class.
+ */
+ at Invariant("true")
+class ContractedClass {
+}
diff --git a/test/com/google/java/contract/tests/EmptyContracts.java b/test/com/google/java/contract/tests/EmptyContracts.java
new file mode 100644
index 0000000..c336aa1
--- /dev/null
+++ b/test/com/google/java/contract/tests/EmptyContracts.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.Requires;
+
+/**
+ * This class exposes some empty contracts that should be
+ * ignored. Note also that not specifying the value is illegal for
+ * a contract annotation, but is not enforced by OpenJDK javac when
+ * using -proc:only; hence the warnings may differ between normal
+ * compilation and contract compilation.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at Invariant({})
+class EmptyContracts {
+ @Requires({})
+ @Ensures({})
+ void emptiness() {
+ }
+}
diff --git a/test/com/google/java/contract/tests/EnclosedExtendsEnclosing.java b/test/com/google/java/contract/tests/EnclosedExtendsEnclosing.java
new file mode 100644
index 0000000..d34c4a0
--- /dev/null
+++ b/test/com/google/java/contract/tests/EnclosedExtendsEnclosing.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+
+/**
+ * A simple enclosed class that extends the enclosing one and is contracted.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class EnclosedExtendsEnclosing {
+ @Invariant("field != 0")
+ static class Enclosed extends EnclosedExtendsEnclosing {
+ private int field;
+ Enclosed(int field) {
+ this.field = field;
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/EnclosedExtendsEnclosingNoContracts.java b/test/com/google/java/contract/tests/EnclosedExtendsEnclosingNoContracts.java
new file mode 100644
index 0000000..9aa106b
--- /dev/null
+++ b/test/com/google/java/contract/tests/EnclosedExtendsEnclosingNoContracts.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package com.google.java.contract.tests;
+
+/**
+ * A simple enclosed class extending the enclosed.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class EnclosedExtendsEnclosingNoContracts {
+ static class Enclosed extends EnclosedExtendsEnclosingNoContracts {
+ private int field;
+ Enclosed(int field) {
+ this.field = field;
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/EnclosedExtendsEnclosingTest.java b/test/com/google/java/contract/tests/EnclosedExtendsEnclosingTest.java
new file mode 100644
index 0000000..3fc5f6c
--- /dev/null
+++ b/test/com/google/java/contract/tests/EnclosedExtendsEnclosingTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package com.google.java.contract.tests;
+
+import com.google.java.contract.InvariantError;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that ContractFinder finds contracts on when an enclosed class extends
+ * the enclosing one.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class EnclosedExtendsEnclosingTest extends TestCase {
+
+ public void testNoContracts() {
+ /*
+ * Simply check if the annotation processor can handle this
+ * configuration. In the past there was bug that caused infinite recursion
+ * with this.
+ */
+ EnclosedExtendsEnclosingNoContracts o =
+ new EnclosedExtendsEnclosingNoContracts.Enclosed(0);
+ }
+
+ public void testContracts() {
+ try {
+ EnclosedExtendsEnclosing o = new EnclosedExtendsEnclosing.Enclosed(0);
+ } catch (InvariantError e) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/EnumTest.java b/test/com/google/java/contract/tests/EnumTest.java
new file mode 100644
index 0000000..09b7086
--- /dev/null
+++ b/test/com/google/java/contract/tests/EnumTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts that apply to enum classes.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class EnumTest extends TestCase {
+ private static enum E {
+ X, Y, Z;
+
+ @Requires("this != Z")
+ public int f() {
+ return this == X ? 1 : 2;
+ }
+ }
+
+ private static enum F {
+ X(1), Y(2), Z(3);
+
+ private F(int x) {
+ }
+
+ @Requires("this != Z")
+ public int f() {
+ return this == X ? 1 : 2;
+ }
+ }
+
+ public void testX() {
+ E e = E.X;
+ e.f();
+ }
+
+ public void testZ() {
+ E e = E.Z;
+ }
+
+ public void testZIllegalState() {
+ E e = E.Z;
+ try {
+ e.f();
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[this != Z]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/ExceptionInPredicateTest.java b/test/com/google/java/contract/tests/ExceptionInPredicateTest.java
new file mode 100644
index 0000000..96f8157
--- /dev/null
+++ b/test/com/google/java/contract/tests/ExceptionInPredicateTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests predicates with checked and unchecked exceptions.
+ *
+ * @author davidmorgan at google.com (David Morgan)
+ */
+public class ExceptionInPredicateTest extends TestCase {
+ @Requires("((Object) null).toString() == null")
+ public void predicateThrowsNullPointerException() {
+ }
+
+ @Requires("Integer.parseInt(\"z\") == 0")
+ public void predicateThrowsNumberFormatException() {
+ }
+
+ public void testRuntimeException() {
+ try {
+ predicateThrowsNullPointerException();
+ fail();
+ } catch (PreconditionError expected) {
+ assertTrue(expected.getMessage().contains("NullPointerException"));
+ }
+ }
+
+ public void testCheckedException() {
+ try {
+ predicateThrowsNumberFormatException();
+ fail();
+ } catch (PreconditionError expected) {
+ assertTrue(expected.getMessage().contains("NumberFormatException"));
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/FinalFieldsTest.java b/test/com/google/java/contract/tests/FinalFieldsTest.java
new file mode 100644
index 0000000..09e70bc
--- /dev/null
+++ b/test/com/google/java/contract/tests/FinalFieldsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that invariants are correctly applied to final fields, which can
+ * be initialized during the constructor call or before.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class FinalFieldsTest extends TestCase {
+ private static interface FinalFieldInterface {
+ public final int FIELD = 42;
+ @Requires("FIELD == 42")
+ public void success();
+ @Requires("FIELD != 42")
+ public void fail();
+ }
+
+ public void testFinalField() {
+ @Invariant("field == 1")
+ class ContractedFinalField {
+ private final int field;
+
+ public ContractedFinalField(int field) {
+ this.field = field;
+ }
+ }
+
+ ContractedFinalField cff = new ContractedFinalField(1);
+ try {
+ new ContractedFinalField(0);
+ } catch (InvariantError e) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testContractedConstant() {
+ @Invariant("FIELD == 1")
+ class ContractedConstant {
+ private static final int FIELD = 1;
+
+ @Requires("FIELD != 1")
+ public void fail() {
+ /* The contract of this method causes it to fail. */
+ }
+ }
+
+ ContractedConstant cc = new ContractedConstant();
+ try {
+ cc.fail();
+ } catch(PreconditionError e) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testContractedFinalDefaultValue() {
+ @Invariant("field == 1")
+ class ContractedFinalDefautValue {
+ private final int field = 1;
+
+ @Requires("field != 1")
+ public void fail() {
+ /* The contract of this method causes it to fail. */
+ }
+ }
+
+ ContractedFinalDefautValue cfdv = new ContractedFinalDefautValue();
+ try {
+ cfdv.fail();
+ } catch (PreconditionError e) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testFinalInterface() {
+ class Implementor implements FinalFieldInterface {
+ @Override
+ public void success() {
+ /* The contract of this method should not throw an exception. */
+ }
+ @Override
+ public void fail() {
+ /* The contract of this method causes it to fail. */
+ }
+ }
+
+ Implementor implementor = new Implementor();
+ implementor.success();
+ try {
+ implementor.fail();
+ } catch (PreconditionError e) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/GenericsTest.java b/test/com/google/java/contract/tests/GenericsTest.java
new file mode 100644
index 0000000..f5f576c
--- /dev/null
+++ b/test/com/google/java/contract/tests/GenericsTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts that apply to generic classes and interfaces.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class GenericsTest extends TestCase {
+ @Invariant("a.length() >= 2")
+ private static class A<T> {
+ protected String a;
+
+ public A(T x) {
+ /* Bogus for some values of x (for example, ""). */
+ a = x.toString();
+ }
+ }
+
+ private static class B extends A<Integer> {
+ /*
+ * Note: the super call is always the *first* thing to be executed
+ * in a constructor; it also means the call is made before any
+ * preconditions. In this case, the invariant of class A gets
+ * executed before the precondition of the constructor of B. This
+ * is a counter-intuitive.
+ */
+ @Requires("x >= 9")
+ public B(int x) {
+ super(x);
+ }
+ }
+
+ private static class S<T> {
+ private T x;
+
+ public S(T x) {
+ this.x = x;
+ }
+
+ @Override
+ public String toString() {
+ return x.toString();
+ }
+ }
+
+ private static class C<U extends S> extends A<U> {
+ public C(U u) {
+ super(u);
+ }
+ }
+
+ private static class D<T> {
+ private A<S<T>> a;
+
+ public D(T x) {
+ a = new C<S<T>>(new S<T>(x));
+ }
+ }
+
+ private static interface I {
+ @Requires("x.toString().length() >= 1")
+ @Ensures("result >= 1")
+ public <T> int f(T x);
+ }
+
+ private static class E implements I {
+ @Override
+ public <T> int f(T x) {
+ return x.toString().length();
+ }
+ }
+
+ private static interface J<T> {
+ @Requires("x.toString().length() >= 2")
+ @Ensures("result >= 2")
+ public int g(T x);
+ }
+
+ private static class F implements J<Integer> {
+ @Override
+ public int g(Integer x) {
+ return x.toString().length();
+ }
+ }
+
+ private static class T<X extends Throwable> {
+ /* Test ability to contract methods that throw type parameters. */
+ @Requires("true")
+ public void f(X x) throws X {
+ throw x;
+ }
+ }
+
+ private static class Npe extends T<NullPointerException> {
+ @Override
+ @Ensures("true")
+ public void f(NullPointerException x) throws NullPointerException {
+ throw x;
+ }
+ }
+
+ public void testB() {
+ B b = new B(48);
+ }
+
+ public void testBBogus() {
+ try {
+ B b = new B(9);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a.length() >= 2]", expected.getMessages().toString());
+ }
+ }
+
+ public void testC() {
+ C<S<Integer>> c = new C<S<Integer>>(new S<Integer>(283));
+ }
+
+ public void testCBogus() {
+ try {
+ C<S<Integer>> c = new C<S<Integer>>(new S<Integer>(3));
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a.length() >= 2]", expected.getMessages().toString());
+ }
+ }
+
+ public void testD() {
+ D<Integer> d = new D<Integer>(new Integer(7892));
+ }
+
+ public void testDBogus() {
+ try {
+ D<Integer> d = new D<Integer>(new Integer(4));
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a.length() >= 2]", expected.getMessages().toString());
+ }
+ }
+
+ public void testE() {
+ E e = new E();
+ e.f(23);
+ }
+
+ public void testEBogus() {
+ try {
+ E e = new E();
+ e.f("");
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x.toString().length() >= 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testF() {
+ F f = new F();
+ f.g(79);
+ }
+
+ public void testFBogus() {
+ try {
+ F f = new F();
+ f.g(1);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x.toString().length() >= 2]", expected.getMessages().toString());
+ }
+ }
+
+ public void testT() {
+ Npe npe = new Npe();
+ try {
+ npe.f(new NullPointerException());
+ fail();
+ } catch (NullPointerException e) {
+ /* Expected exception. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/InheritanceTest.java b/test/com/google/java/contract/tests/InheritanceTest.java
new file mode 100644
index 0000000..4cf8535
--- /dev/null
+++ b/test/com/google/java/contract/tests/InheritanceTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+import junit.framework.TestCase;
+
+/**
+ * Tests contract inheritance through superclasses and interfaces.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at SuppressWarnings("unused")
+public class InheritanceTest extends TestCase {
+ @Invariant("a >= 0")
+ private abstract static class A {
+ private int p;
+ protected int a;
+
+ @Requires("x >= 0")
+ public int f(int x) {
+ a = x;
+ return x;
+ }
+
+ @Requires("x == 1")
+ @Ensures("result >= old (x)")
+ public abstract int g(int x);
+
+ @Ensures("p == old (p)")
+ public void q() {
+ }
+ }
+
+ @Invariant("b >= 0 || b == -1")
+ private static class B extends A {
+ protected int b;
+
+ @Override
+ @Requires("x == -1")
+ @Ensures("result >= old (x)")
+ public int f(int x) {
+ /* Bogus for x == -1. */
+ a = b = x;
+ return x;
+ }
+
+ @Override
+ public int g(int x) {
+ return x + 1;
+ }
+
+ /*
+ * Tests that private fields are accessible from within old-value
+ * expressions even when inherited.
+ */
+ @Override
+ public void q() {
+ }
+ }
+
+ private static class C extends B {
+ @Override
+ @Requires("x >= 1")
+ public int g(int x) {
+ /* Bogus. */
+ return x - 1;
+ }
+ }
+
+ @Invariant("k() == 1")
+ private static interface I {
+ int k();
+
+ @Requires("y > x")
+ @Ensures("result > 0")
+ int h(int x, int y);
+ }
+
+ private abstract static class J implements I {
+ @Override
+ public int k() {
+ return 1;
+ }
+
+ @Override
+ public int h(int x, int y) {
+ return y - x;
+ }
+ }
+
+ private static class D extends B implements I {
+ /* Necessary so that invariants will apply. */
+ public D() {
+ }
+
+ @Override
+ public int k() {
+ return 1;
+ }
+
+ @Override
+ public int h(int x, int y) {
+ return y - x;
+ }
+ }
+
+ private static class E extends B implements I {
+ /* Necessary so that invariants will apply. */
+ public E() {
+ }
+
+ @Override
+ public int k() {
+ /* Bogus. */
+ return 2;
+ }
+
+ @Override
+ public int h(int x, int y) {
+ return y - x;
+ }
+ }
+
+ private static class F extends B implements I {
+ /* Necessary so that invariants will apply. */
+ public F() {
+ }
+
+ @Override
+ public int k() {
+ return 1;
+ }
+
+ @Override
+ @Requires("y >= x")
+ public int h(int x, int y) {
+ /* Bogus for x == y. */
+ return y - x;
+ }
+ }
+
+ /*
+ * Complicated way to get h() through an interface call.
+ */
+ private static int getK(I obj) {
+ return obj.k();
+ }
+
+ @Invariant("getK(this) == 1")
+ private static class G extends J {
+ /* Necessary so that invariants will apply. */
+ public G() {
+ }
+ }
+
+ @Invariant("getK(this) == 2")
+ private static class H extends J {
+ /* Necessary so that invariants will apply. */
+ public H() {
+ }
+ }
+
+ B b;
+ C c;
+
+ D d;
+ F f;
+
+ @Override
+ protected void setUp() {
+
+ b = new B();
+ c = new C();
+
+ d = new D();
+ f = new F();
+ }
+
+ public void testBF() {
+ assertEquals(b.f(42), 42);
+ }
+
+ public void testBFInvalidArgument() {
+ try {
+ b.f(-36);
+ fail();
+ } catch (PreconditionError expected) {
+ /*
+ * TODO(lenh): Ordering of the messages is not predictable at
+ * the moment; the exception types should be extended with a
+ * getter to return the message array instead of just a message.
+ */
+ assertEquals("[x >= 0, x == -1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBFBogusArgument() {
+ try {
+ b.f(-1);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBG() {
+ assertEquals(b.g(1), 2);
+ }
+
+ public void testBGInvalidArgument() {
+ try {
+ b.g(3);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x == 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBQ() {
+ b.q();
+ }
+
+ public void testCGBogus() {
+ try {
+ c.g(735);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[result >= old (x)]", expected.getMessages().toString());
+ }
+ }
+
+ public void testDFBogusArgument() {
+ try {
+ d.f(-1);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[a >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testDH() {
+ assertEquals(d.h(37, 56), 19);
+ }
+
+ public void testEBogusInvariant() {
+ try {
+ E e = new E();
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[k() == 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testFHBogusArgument() {
+ try {
+ f.h(328, 328);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[result > 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testG() {
+ G g = new G();
+ }
+
+ public void testH() {
+ try {
+ H h = new H();
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[getK(this) == 2]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/InnerAnnotationTest.java b/test/com/google/java/contract/tests/InnerAnnotationTest.java
new file mode 100644
index 0000000..b757ee3
--- /dev/null
+++ b/test/com/google/java/contract/tests/InnerAnnotationTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that the annotation processor handles annotations defined inside
+ * contracted classes.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class InnerAnnotationTest extends TestCase {
+ @Invariant("true")
+ private static class DefinesInnerAnnotation {
+ public static @interface MyAnnotation {
+ String value() default "none";
+ }
+ @MyAnnotation
+ public int x;
+ }
+
+ DefinesInnerAnnotation dia;
+
+ public void testInnerAnnotation() {
+ dia = new DefinesInnerAnnotation();
+ }
+}
diff --git a/test/com/google/java/contract/tests/MemberContractsTest.java b/test/com/google/java/contract/tests/MemberContractsTest.java
new file mode 100644
index 0000000..8213107
--- /dev/null
+++ b/test/com/google/java/contract/tests/MemberContractsTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests simple contracts that apply to class members. This test case
+ * does not include member contracts that depend on special features
+ * such as {@code old}, {@code result} or {@code signal}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class MemberContractsTest extends TestCase {
+ @Invariant("count >= 0")
+ static class MemberContracts {
+ private int count;
+
+ public MemberContracts() {
+ count = 0;
+ }
+
+ @Requires("n >= 0")
+ public int naturalIdentity(int n) {
+ return n;
+ }
+
+ /*
+ * Used to check the internal value, to make sure
+ * contracts do not alter normal behavior.
+ */
+ public int getCount() {
+ return count;
+ }
+
+ public void add(int n) {
+ count += n;
+ }
+
+ @Requires("n >= 1")
+ @Ensures("count >= 1")
+ public void addPositive(int n) {
+ count += n;
+ }
+
+ @Requires("n >= 1")
+ @Ensures("count >= 1")
+ public void addPositiveBogus(int n) {
+ /* Error on purpose. */
+ count += n - 1;
+ }
+
+ public static boolean allNatural(int[] array) {
+ for (int n : array) {
+ if (n < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Requires("allNatural(array)")
+ public void addNaturals(int... array) {
+ for (int n : array) {
+ add(n);
+ }
+ }
+ }
+
+ MemberContracts sample;
+
+ @Override
+ protected void setUp() {
+ sample = new MemberContracts();
+ }
+
+ public void testIdentitySuccess() {
+ assertEquals(sample.naturalIdentity(42), 42);
+ }
+
+ public void testIdentityFailure() {
+ try {
+ sample.naturalIdentity(-36);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[n >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testInvariantSuccess() {
+ sample.add(71);
+ assertEquals(sample.getCount(), 71);
+ }
+
+ public void testInvariantFailure() {
+ try {
+ sample.add(-12);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[count >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testAddChainFailure() {
+ try {
+ sample.add(234);
+ sample.add(-123);
+ sample.add(-200);
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[count >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testAddPositive() {
+ sample.addPositive(42);
+ assertEquals(sample.getCount(), 42);
+ }
+
+ public void testAddPositiveBogusSuccess() {
+ sample.addPositiveBogus(42);
+ }
+
+ public void testAddPositiveBogusFailure() {
+ try {
+ sample.addPositiveBogus(1);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[count >= 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testAddNaturalsSuccess() {
+ sample.addNaturals(1, 2, 3, 4);
+ assertEquals(sample.getCount(), 10);
+ }
+
+ public void testAddNaturalsFailure() {
+ try {
+ sample.addNaturals(1, 2, 3, -4, 5);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[allNatural(array)]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/NestedClassTest.java b/test/com/google/java/contract/tests/NestedClassTest.java
new file mode 100644
index 0000000..59aedde
--- /dev/null
+++ b/test/com/google/java/contract/tests/NestedClassTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts that apply to a nested class.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class NestedClassTest extends TestCase {
+ static class NestedClass {
+ @Requires("true")
+ public void nestedSuccess() {
+ }
+
+ @Requires("false")
+ public void nestedFailure() {
+ }
+
+ @Requires({ "true", "true && true" })
+ @Ensures({ "true || true", "true" })
+ public void nestedMultiSuccess() {
+ }
+
+ /*
+ * This test checks that Contracts for Java is doing its job
+ * right when parsing the contract method names that
+ * contain '$' and identifies the correct failing
+ * predicate.
+ */
+ @Requires({ "true", "true && true" })
+ @Ensures({ "true && false", "true" })
+ public void nestedSingleFailure() {
+ }
+
+ @Requires("getTrue()")
+ public void outerAccessSuccess() {
+ }
+
+ @Requires("getFalse()")
+ public void outerAccessFailure() {
+ }
+ }
+
+ private static boolean getTrue() {
+ return true;
+ }
+
+ private static boolean getFalse() {
+ return false;
+ }
+
+ NestedClass sample;
+
+ @Override
+ protected void setUp() {
+ sample = new NestedClass();
+ }
+
+ public void testNestedSuccess() {
+ sample.nestedSuccess();
+ }
+
+ public void testNestedFailure() {
+ try {
+ sample.nestedFailure();
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[false]", expected.getMessages().toString());
+ }
+ }
+
+ public void testNestedMultiSuccess() {
+ sample.nestedMultiSuccess();
+ }
+
+ public void testNestedSingleFailure() {
+ try {
+ sample.nestedSingleFailure();
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[true && false]", expected.getMessages().toString());
+ }
+ }
+
+ public void testOuterAccessSuccess() {
+ sample.outerAccessSuccess();
+ }
+
+ public void testOuterAccessFailure() {
+ try {
+ sample.outerAccessFailure();
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[getFalse()]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/PatternMapTest.java b/test/com/google/java/contract/tests/PatternMapTest.java
new file mode 100644
index 0000000..01662df
--- /dev/null
+++ b/test/com/google/java/contract/tests/PatternMapTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.core.util.PatternMap;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for {@link PatternMap}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class PatternMapTest extends TestCase {
+ private PatternMap<Integer> map;
+
+ @Override
+ protected void setUp() {
+ map = new PatternMap<Integer>();
+ }
+
+ public void testExactMatch() {
+ map.put("a.b.c.X", 0);
+ map.put("a.b.Y", 1);
+ map.put("a.b.c.d.Z", 2);
+ map.put("a.b.Y1", 3);
+ assertEquals(2, (int) map.get("a.b.c.d.Z"));
+ map.put("a.b.Y2", 4);
+ map.put("a.b.c.d.Z1", 5);
+ assertEquals(1, (int) map.get("a.b.Y"));
+ assertEquals(4, (int) map.get("a.b.Y2"));
+ }
+
+ public void testStarMatch() {
+ map.put("a.b.c.*", 0);
+ map.put("a.b.Y", 1);
+ map.put("a.b.c.d.Z", 2);
+ map.put("a.b.Y1", 3);
+ assertEquals(0, (int) map.get("a.b.c.X"));
+ assertEquals(2, (int) map.get("a.b.c.d.Z"));
+ map.put("a.b.Y2", 4);
+ map.put("a.b.c.d.Z1", 5);
+ assertEquals(0, (int) map.get("a.b.c.U"));
+ assertEquals(4, (int) map.get("a.b.Y2"));
+ }
+
+ public void testStarOverride() {
+ map.put("a.b.Y", 1);
+ map.put("a.b.c.d.Z", 2);
+ map.put("a.b.c.*", 0);
+ map.put("a.b.Y1", 3);
+ assertEquals(0, (int) map.get("a.b.c.X"));
+ assertEquals(0, (int) map.get("a.b.c.d.Z"));
+ map.put("a.b.Y2", 4);
+ map.put("a.b.c.d.Z1", 5);
+ assertEquals(0, (int) map.get("a.b.c.U"));
+ assertEquals(4, (int) map.get("a.b.Y2"));
+ }
+
+ public void testRedundantOverride() {
+ map.put("a.*", 0);
+ map.put("a.x.*", 1);
+ map.put("a.x.u.*", 2);
+ map.put("a.y.*", 0);
+ map.put("a.y.u.*", 0);
+ assertEquals(1, (int) map.get("a.x.X"));
+ assertEquals(0, (int) map.get("a.y.X"));
+ assertEquals(0, (int) map.get("a.a.X"));
+ assertEquals(false, map.isOverriden("a.x.u.X"));
+ assertEquals(false, map.isOverriden("a.x.u.*"));
+ assertEquals(true, map.isOverriden("a.*"));
+ assertEquals(true, map.isOverriden("a.x.*"));
+ assertEquals(false, map.isOverriden("a.y.*"));
+ }
+}
diff --git a/test/com/google/java/contract/tests/PublicCallTest.java b/test/com/google/java/contract/tests/PublicCallTest.java
new file mode 100644
index 0000000..c0e9f01
--- /dev/null
+++ b/test/com/google/java/contract/tests/PublicCallTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts on public methods for reentry.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class PublicCallTest extends TestCase {
+ @Invariant("x != 0")
+ private static class SimpleObject {
+ public int x;
+
+ public SimpleObject() {
+ x = 1;
+ }
+
+ public void f() {
+ }
+
+ public void g() {
+ x = 0;
+ f();
+ x = 1;
+ }
+
+ public void f1() {
+ x = 0;
+ }
+
+ public void g1() {
+ f1();
+ x = 1;
+ }
+ }
+
+ @Invariant("x != 0")
+ private static class A {
+ public int x;
+ }
+
+ private static class B extends A {
+ public B() {
+ x = 1;
+ }
+ }
+
+ protected SimpleObject sample;
+
+ @Override
+ protected void setUp() {
+ sample = new SimpleObject();
+ }
+
+ public void testF() {
+ sample.x = 1;
+ sample.f();
+ }
+
+ public void testG() {
+ sample.x = 1;
+ sample.g();
+ }
+
+ public void testF1() {
+ try {
+ sample.x = 1;
+ sample.f1();
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[x != 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testG1() {
+ sample.x = 1;
+ sample.g1();
+ }
+
+ public void testA() {
+ try {
+ A a = new A();
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[x != 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testB() {
+ B b = new B();
+ }
+}
diff --git a/test/com/google/java/contract/tests/ReturnTypeTest.java b/test/com/google/java/contract/tests/ReturnTypeTest.java
new file mode 100644
index 0000000..cfcd980
--- /dev/null
+++ b/test/com/google/java/contract/tests/ReturnTypeTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.PostconditionError;
+
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+
+/**
+ * Tests the correct typing of result variables through contract
+ * inheritance.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+ at SuppressWarnings("unused")
+public class ReturnTypeTest extends TestCase {
+ private static class A {
+ @Ensures({
+ "result != null",
+ "result.equals(x)"
+ })
+ public Number f(int x) {
+ return x;
+ }
+ }
+
+ private static class B extends A {
+ /* Covariant return type. */
+ @Override
+ public Integer f(int x) {
+ /* Bogus. */
+ return x + 1;
+ }
+ }
+
+ private static interface I<T> {
+ @Ensures({
+ "result != null",
+ "result.equals(x)"
+ })
+ public T f(int x);
+ }
+
+ private static class C implements I<Integer> {
+ /* Specialized return type. */
+ @Override
+ public Integer f(int x) {
+ /* Bogus. */
+ return x - 1;
+ }
+ }
+
+ private static class C1 implements I<Number> {
+ /* Specialized and covariant return type. */
+ @Override
+ public Integer f(int x) {
+ /* Bogus. */
+ return x - 1;
+ }
+ }
+
+ private static class C2 extends A implements I<Comparable> {
+ /* Specialized and doubly covariant return type. */
+ @Override
+ public Integer f(int x) {
+ /* Bogus. */
+ return x - 1;
+ }
+ }
+
+ /* Complex erasure from a superclass. */
+ private abstract static class J<T extends Serializable & Comparable> {
+ @Ensures({
+ "result != null",
+ "result.compareTo(x) == 0"
+ })
+ public abstract T f(int x);
+ }
+
+ private static class D extends J<Integer> {
+ /* Specialized return type. */
+ @Override
+ public Integer f(int x) {
+ /* Bogus. */
+ return x - 1;
+ }
+ }
+
+ public void testB() {
+ try {
+ new B().f(8326);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testC() {
+ try {
+ new C().f(584);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testC1() {
+ try {
+ new C1().f(206);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testC2() {
+ try {
+ new C2().f(4896);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+
+ public void testD() {
+ try {
+ new D().f(9075);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SelectiveContractsTest.java b/test/com/google/java/contract/tests/SelectiveContractsTest.java
new file mode 100644
index 0000000..36c5461
--- /dev/null
+++ b/test/com/google/java/contract/tests/SelectiveContractsTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.ContractEnvironment;
+import com.google.java.contract.Ensures;
+import com.google.java.contract.Invariant;
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests selective contract activation.
+ *
+ * @see Cofoja
+ */
+public class SelectiveContractsTest extends TestCase {
+ private static class A {
+ @Requires("false")
+ public static void f() {
+ }
+ }
+
+ private static class B {
+ @Ensures("false")
+ public static void f() {
+ }
+ }
+
+ @Invariant("false")
+ private static class C {
+ }
+
+ private static class D {
+ @Requires("false")
+ @Ensures("false")
+ public static void f() {
+ }
+ }
+
+ @Invariant("false")
+ private static class E {
+ @Requires("false")
+ @Ensures("false")
+ public void f() {
+ }
+ }
+
+ @Invariant("false")
+ private static class F {
+ @Requires("false")
+ public void f() {
+ }
+ }
+
+ @Invariant("false")
+ private static interface I {
+ }
+
+ /* Missing @Contracted. */
+ private static class G implements I {
+ }
+
+ @Override
+ protected void setUp() {
+ ContractEnvironment env = Cofoja.contractEnv;
+
+ env.disablePreconditions("com.google.java.contract.tests.SelectiveContractsTest$A");
+ env.disablePostconditions("com.google.java.contract.tests.SelectiveContractsTest$B");
+ env.disableInvariants("com.google.java.contract.tests.SelectiveContractsTest$C");
+ env.disablePreconditions("com.google.java.contract.tests.SelectiveContractsTest$D");
+ env.disableInvariants("com.google.java.contract.tests.SelectiveContractsTest$E");
+
+ env.ignore("com.google.java.contract.tests.SelectiveContractsTest$F");
+ env.ignore("com.google.java.contract.tests.SelectiveContractsTest$G");
+
+ env.disableInvariants("com.google.java.contract.tests.selective.a.*");
+ env.disableInvariants("com.google.java.contract.tests.selective.b.*");
+ env.enableInvariants("com.google.java.contract.tests.selective.b.y.*");
+ env.enableInvariants("com.google.java.contract.tests.selective.a.x.X1");
+ }
+
+ public void testA() {
+ A.f();
+ }
+
+ public void testB() {
+ B.f();
+ }
+
+ public void testC() {
+ new C();
+ }
+
+ public void testD() {
+ try {
+ D.f();
+ fail();
+ } catch (PostconditionError expected) {
+ /* Expected since the precondition should be disabled. */
+ }
+ }
+
+ public void testE() {
+ try {
+ new E().f();
+ fail();
+ } catch (PreconditionError expected) {
+ /* Expected since the invariant should be disabled. */
+ }
+ }
+
+ public void testF() {
+ new F().f();
+ }
+
+ public void testG() {
+ new G();
+ }
+
+ public void testStarPattern() {
+ new com.google.java.contract.tests.selective.a.A();
+ new com.google.java.contract.tests.selective.a.x.X();
+ }
+
+ public void testOverridePattern() {
+ try {
+ new com.google.java.contract.tests.selective.a.x.X1();
+ } catch (InvariantError expected) {
+ /* Expected since the star pattern should be overriden. */
+ }
+ }
+
+ public void testOverrideStarPattern() {
+ new com.google.java.contract.tests.selective.b.B();
+ try {
+ new com.google.java.contract.tests.selective.b.y.Y();
+ } catch (InvariantError expected) {
+ /* Expected since the first star pattern should be overriden. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateGenericSuperclass.java b/test/com/google/java/contract/tests/SeparateGenericSuperclass.java
new file mode 100644
index 0000000..d0d23fe
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateGenericSuperclass.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+
+/**
+ * A dummy class overriden in {@link SeparateGenericSuperclassTest}.
+ * It has some type parameter with a complex erasure.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+abstract class SeparateGenericSuperclass<T extends Number & Comparable> {
+ @Ensures({
+ "result != null",
+ "result.equals(x)"
+ })
+ public abstract T f(int x);
+}
diff --git a/test/com/google/java/contract/tests/SeparateGenericSuperclassTest.java b/test/com/google/java/contract/tests/SeparateGenericSuperclassTest.java
new file mode 100644
index 0000000..a51b899
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateGenericSuperclassTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PostconditionError;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests inheritance of postconditions of methods returning a generic
+ * type parameter with complex erasure in a superclass compiled
+ * separately.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class SeparateGenericSuperclassTest extends TestCase {
+ private static class SeparateChild
+ extends SeparateGenericSuperclass<Integer> {
+ @Override
+ public Integer f(int x) {
+ return x + 1;
+ }
+ }
+
+ public void testF() {
+ try {
+ new SeparateChild().f(6379);
+ fail();
+ } catch (PostconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateInterface.java b/test/com/google/java/contract/tests/SeparateInterface.java
new file mode 100644
index 0000000..34c5744
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateInterface.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 Johannes Rieken
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Requires;
+
+/**
+ * A dummy interface overriden in {@link SeparateInterfaceTest}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+interface SeparateInterface {
+ @Requires("x >= 0")
+ public void f(int x);
+
+ @Requires("x < y")
+ public void g(int x, int y);
+
+ @Requires("x < y + z")
+ public void h(int x, int y, int z);
+
+ @Requires("x > y")
+ public void k(int x, int y);
+}
diff --git a/test/com/google/java/contract/tests/SeparateInterfaceTest.java b/test/com/google/java/contract/tests/SeparateInterfaceTest.java
new file mode 100644
index 0000000..b5a78e4
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateInterfaceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PreconditionError;
+import junit.framework.TestCase;
+
+/**
+ * Tests renaming of interface method parameters.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class SeparateInterfaceTest extends TestCase {
+ private static class SeparateImplementation implements SeparateInterface {
+ @Override
+ public void f(int y) {
+ }
+
+ @Override
+ public void g(int y, int x) {
+ }
+
+ @Override
+ public void h(int y, int z, int x) {
+ }
+
+ @Override
+ public void k(int x, int y) {
+ }
+ }
+
+ private SeparateImplementation sample;
+
+ @Override
+ protected void setUp() {
+ sample = new SeparateImplementation();
+ }
+
+ public void testF() {
+ sample.f(846);
+ }
+
+ public void testFIllegalArgument() {
+ try {
+ sample.f(-67);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testG() {
+ sample.g(28, 371);
+ }
+
+ public void testGIllegalArgument() {
+ try {
+ sample.g(2485, 846);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x < y]", expected.getMessages().toString());
+ }
+ }
+
+ public void testH() {
+ sample.h(2, 1, 3);
+ }
+
+ public void testHIllegalArgument() {
+ try {
+ sample.h(3, 1, 2);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x < y + z]", expected.getMessages().toString());
+ }
+ }
+
+ public void testK() {
+ sample.k(81, 67);
+ }
+
+ public void testKIllegalArgument() {
+ try {
+ sample.k(67, 81);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x > y]", expected.getMessages().toString());
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateInvariantSuperclass.java b/test/com/google/java/contract/tests/SeparateInvariantSuperclass.java
new file mode 100644
index 0000000..9ac58bb
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateInvariantSuperclass.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Invariant;
+
+/**
+ * Separate dummy class to test contract checking through inheritance.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+ at Invariant("x != 0")
+public class SeparateInvariantSuperclass {
+ @SuppressWarnings("unused")
+ protected int x = 0;
+
+ public SeparateInvariantSuperclass() {
+ x = 1;
+ }
+
+ public void violate() {
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateInvariantSuperclassTest.java b/test/com/google/java/contract/tests/SeparateInvariantSuperclassTest.java
new file mode 100644
index 0000000..6d9742f
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateInvariantSuperclassTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.InvariantError;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Tests that contracts are found through inheritance when declared as
+ * invariants on the superclass.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class SeparateInvariantSuperclassTest extends TestCase{
+ private static class SeparateChild extends SeparateInvariantSuperclass {
+ @Override
+ public void violate() {
+ x = 0;
+ }
+ }
+
+ private SeparateChild child;
+
+ @Override
+ protected void setUp() {
+ child = new SeparateChild();
+ }
+
+ public void testViolate() {
+ try {
+ child.violate();
+ fail();
+ } catch (InvariantError expected) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateMethodContractSuperclass.java b/test/com/google/java/contract/tests/SeparateMethodContractSuperclass.java
new file mode 100644
index 0000000..26cea51
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateMethodContractSuperclass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Requires;
+
+/**
+ * Separate dummy class to test contract checking through inheritance.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class SeparateMethodContractSuperclass {
+ @Requires("false")
+ public void violate() {
+ }
+}
diff --git a/test/com/google/java/contract/tests/SeparateMethodContractSuperclassTest.java b/test/com/google/java/contract/tests/SeparateMethodContractSuperclassTest.java
new file mode 100644
index 0000000..bab6814
--- /dev/null
+++ b/test/com/google/java/contract/tests/SeparateMethodContractSuperclassTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package com.google.java.contract.tests;
+
+import com.google.java.contract.PreconditionError;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that contracts are found through inheritance when declared as
+ * method contracts on the superclass.
+ *
+ * @author chatain at google.com (Leonardo Chatain)
+ */
+public class SeparateMethodContractSuperclassTest extends TestCase {
+ private static class SeparateChild extends SeparateMethodContractSuperclass{
+ @Override
+ public void violate() {
+
+ }
+ }
+
+ private SeparateChild child;
+
+ @Override
+ protected void setUp() {
+ child = new SeparateChild();
+ }
+
+ public void testViolate() {
+ try {
+ child.violate();
+ fail();
+ } catch (PreconditionError expected) {
+ /* Bogus implementation. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/SimpleMathTest.java b/test/com/google/java/contract/tests/SimpleMathTest.java
new file mode 100644
index 0000000..7868f3b
--- /dev/null
+++ b/test/com/google/java/contract/tests/SimpleMathTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+import com.google.java.contract.ThrowEnsures;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts on some simple mathematical functions. Among other
+ * things, these contracts make use of {@code result} and {@code old}.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class SimpleMathTest extends TestCase {
+ private static class SimpleMath {
+ /*
+ * This is just for the test; usually, you should not reference
+ * the parameters directly, and use old() instead.
+ * TODO(lenh): Should we be able to do that at all?
+ */
+ @Ensures("result == x + y")
+ public static int add(int x, int y) {
+ return x + y;
+ }
+
+ @Ensures("result == x + y")
+ public static int bogusAdd(int x, int y) {
+ return x + y + 1;
+ }
+
+ @Requires({ "x > 0", "y > 0" })
+ @Ensures({
+ "result != 0",
+ "old (x) % result == 0",
+ "old (y) % result == 0"
+ })
+ public static int gcd(int x, int y) {
+ while (x != 0 && y != 0) {
+ if (x > y) {
+ x -= y;
+ } else {
+ y -= x;
+ }
+ }
+
+ return (x != 0) ? x : y;
+ }
+
+ @Requires({ "x > 0", "y > 0" })
+ @Ensures({
+ "result != 0",
+ "old (x) % result == 0",
+ "old (y) % result == 0"
+ })
+ public static int bogusGcd(int x, int y) {
+ while (x != 0 && y != 0) {
+ if (x > y) {
+ x -= y;
+ } else {
+ y -= x;
+ }
+ }
+
+ return (x != 0) ? y : x;
+ }
+
+ @Requires({ "x > 0", "y > 0" })
+ @Ensures({
+ "result != 0",
+ "old (x) % result == 0",
+ "old (y) % result == 0"
+ })
+ public static int bogusGcd1(int x, int y) {
+ return x;
+ }
+
+ @Requires("n >= 0")
+ @Ensures("!result || old (n) % 2 == 0")
+ public static boolean even(int n) {
+ if (n == 0) {
+ return true;
+ } else {
+ return odd(n - 1);
+ }
+ }
+
+ @Requires("n >= 0")
+ @Ensures({
+ "!result || old (n) % 2 == 1",
+ /* For testing purposes: check that old () has the right value. */
+ "old (n) == n"
+ })
+ public static boolean odd(int n) {
+ if (n == 0) {
+ return false;
+ } else {
+ return even(n - 1);
+ }
+ }
+
+ @ThrowEnsures({ "IllegalArgumentException", "old (x) < 0" })
+ public static double sqrt(double x) {
+ if (x < 0) {
+ throw new IllegalArgumentException();
+ }
+ return Math.sqrt(x);
+ }
+
+ @ThrowEnsures({ "IllegalArgumentException", "old (x) < -1" })
+ public static double bogusSqrt(double x) {
+ if (x < 0) {
+ throw new IllegalArgumentException();
+ }
+ return Math.sqrt(x);
+ }
+ }
+
+ public void testAdd() {
+ assertEquals(SimpleMath.add(1, 2), 3);
+ }
+
+ public void testBogusAdd() {
+ try {
+ SimpleMath.bogusAdd(1, 2);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[result == x + y]", expected.getMessages().toString());
+ }
+ }
+
+ public void testGcd() {
+ assertEquals(SimpleMath.gcd(83295, 37285), 5);
+ }
+
+ public void testGcdInvalidArguments() {
+ try {
+ SimpleMath.gcd(-1382, -3287);
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[x > 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBogusGcd() {
+ try {
+ SimpleMath.bogusGcd(83295, 37285);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[result != 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBogusGcd1() {
+ try {
+ SimpleMath.bogusGcd1(83295, 37285);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[old (y) % result == 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testEven() {
+ assertEquals(SimpleMath.even(392), true);
+ }
+
+ public void testSqrt() {
+ SimpleMath.sqrt(4);
+ }
+
+ public void testSqrtInvalidArgument() {
+ try {
+ SimpleMath.sqrt(-3);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ /* Expected to pass through the contract. */
+ }
+ }
+
+ public void testBogusSqrt() {
+ try {
+ SimpleMath.bogusSqrt(-0.5);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals(expected.getMessages().toString(),
+ "[IllegalArgumentException => old (x) < -1]");
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/StackTest.java b/test/com/google/java/contract/tests/StackTest.java
new file mode 100644
index 0000000..c6e64cc
--- /dev/null
+++ b/test/com/google/java/contract/tests/StackTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.InvariantError;
+import com.google.java.contract.PostconditionError;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.examples.ArrayListStack;
+import com.google.java.contract.examples.Stack;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests implementations of {@link Stack} against the contracts
+ * specified in the interface.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class StackTest extends TestCase {
+ /**
+ * A stack implementation that does not preserve its invariant.
+ */
+ public static class BogusInvariantStack<T> implements Stack<T> {
+ @Override
+ public int size() {
+ return -1;
+ }
+
+ @Override
+ public T peek() {
+ return null;
+ }
+
+ @Override
+ public T pop() {
+ return null;
+ }
+
+ @Override
+ public void push(T obj) {
+ }
+ }
+
+ /**
+ * A stack implementation that does not preserve its postconditions.
+ */
+ public static class BogusPostconditionsStack<T>
+ extends BogusInvariantStack<T> {
+ @Override
+ public int size() {
+ return 0;
+ }
+ }
+
+ /**
+ * A stack implementation that simply counts its elements instead of
+ * storing them.
+ */
+ public static class BogusCountingStack<T>
+ extends BogusPostconditionsStack<T> {
+ protected int count;
+
+ public BogusCountingStack() {
+ count = 0;
+ }
+
+ @Override
+ public int size() {
+ return count;
+ }
+
+ @Override
+ public T pop() {
+ --count;
+ return null;
+ }
+
+ @Override
+ public void push(T obj) {
+ ++count;
+ }
+ }
+
+ /**
+ * A stack implementation that simply stores the last element. This
+ * implementation suffices to fool our contracts.
+ */
+ public static class BogusLastElementStack<T>
+ extends BogusCountingStack<T> {
+ protected T lastElement;
+
+ @Override
+ public T peek() {
+ return lastElement;
+ }
+
+ @Override
+ public T pop() {
+ --count;
+ return lastElement;
+ }
+
+ @Override
+ public void push(T obj) {
+ ++count;
+ lastElement = obj;
+ }
+ }
+
+ private Stack<Integer> stack;
+
+ @Override
+ public void setUp() {
+ stack = new ArrayListStack<Integer>();
+ }
+
+ public void testNormal() {
+ stack.push(1);
+ stack.push(2);
+ assertEquals(2, stack.size());
+ stack.pop();
+ stack.push(3);
+ stack.pop();
+ stack.pop();
+ stack.push(4);
+ assertEquals(1, stack.size());
+ }
+
+ public void testEmptyPop() {
+ try {
+ stack.pop();
+ fail();
+ } catch (PreconditionError expected) {
+ assertEquals("[size() >= 1]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBogusInvariant() {
+ try {
+ Stack<Integer> bogusInvariantStack = new BogusInvariantStack<Integer>();
+ fail();
+ } catch (InvariantError expected) {
+ assertEquals("[size() >= 0]", expected.getMessages().toString());
+ }
+ }
+
+ public void testBogusPostPush() {
+ Stack<Integer> bogusPostconditionsStack =
+ new BogusPostconditionsStack<Integer>();
+ try {
+ bogusPostconditionsStack.push(1);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[size() == old (size()) + 1]",
+ expected.getMessages().toString());
+ }
+ }
+
+ public void testBogusPostPush1() {
+ Stack<Integer> countingStack = new BogusCountingStack<Integer>();
+ try {
+ countingStack.push(1);
+ fail();
+ } catch (PostconditionError expected) {
+ assertEquals("[peek() == old (obj)]", expected.getMessages().toString());
+ }
+ }
+
+ public void testIneffectiveBogusPostPush() {
+ Stack<Integer> lastElementStack = new BogusLastElementStack<Integer>();
+ lastElementStack.push(1);
+ lastElementStack.push(2);
+ lastElementStack.pop();
+ }
+}
diff --git a/test/com/google/java/contract/tests/VariadicTest.java b/test/com/google/java/contract/tests/VariadicTest.java
new file mode 100644
index 0000000..4ea1855
--- /dev/null
+++ b/test/com/google/java/contract/tests/VariadicTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 Nhat Minh Lê
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests;
+
+import com.google.java.contract.Ensures;
+import com.google.java.contract.PreconditionError;
+import com.google.java.contract.Requires;
+import junit.framework.TestCase;
+
+/**
+ * Tests contracts with classes that use variadic methods.
+ *
+ * @author nhat.minh.le at huoc.org (Nhat Minh Lê)
+ */
+public class VariadicTest extends TestCase {
+ private static class V {
+ @Requires("xs.length >= 2")
+ public int sub(int... xs) {
+ int acc = xs[0];
+ for (int i = 1; i < xs.length; ++i) {
+ acc -= xs[i];
+ }
+ return acc;
+ }
+
+ @Ensures("xs.length < 2 || result == sub(xs)")
+ public int subOrNeg(int... xs) {
+ switch (xs.length) {
+ case 0:
+ return 0;
+ case 1:
+ return -xs[0];
+ default:
+ int acc = xs[0];
+ for (int i = 1; i < xs.length; ++i) {
+ acc -= xs[i];
+ }
+ return acc;
+ }
+ }
+
+ public int addMultiSub(int[]... xss) {
+ int acc = 0;
+ for (int i = 0; i < xss.length; ++i) {
+ acc += sub(xss[i]);
+ }
+ return acc;
+ }
+
+ public boolean testSubs(int... xs) {
+ return xs.length < 2 ? true : sub(xs) == subOrNeg(xs);
+ }
+
+ @Requires("testSubs(1, 2, 3, 4, 5, 6)")
+ public void variadicInContract() {
+ }
+
+ @Requires("addMultiSub(new int[] { 3, 2, 1 }, new int[] { 1, 2, 3 }) == -4")
+ public void multiSubVariadicInContract() {
+ }
+
+ @Requires("false")
+ public void buggyVariadic(int... xs) {
+ }
+ }
+
+ private V v;
+
+ @Override
+ public void setUp() {
+ v = new V();
+ }
+
+ public void testSub() {
+ assertEquals(v.sub(3, 2, 1), 0);
+ }
+
+ public void testSubOrNeg() {
+ assertEquals(v.subOrNeg(3, 2, 1), 0);
+ assertEquals(v.subOrNeg(4), -4);
+ }
+
+ public void testTestSubs() {
+ assertTrue(v.testSubs(8932, 678, 20122));
+ }
+
+ public void testVariadicInContract() {
+ v.variadicInContract();
+ }
+
+ public void testMultiSubVariadicInContract() {
+ v.multiSubVariadicInContract();
+ }
+
+ public void testBuggyVariadic() {
+ try {
+ v.buggyVariadic(8932, 678, 20122);
+ fail();
+ } catch (PreconditionError e) {
+ /* Expected. */
+ }
+ }
+}
diff --git a/test/com/google/java/contract/tests/selective/a/A.java b/test/com/google/java/contract/tests/selective/a/A.java
new file mode 100644
index 0000000..be6fe87
--- /dev/null
+++ b/test/com/google/java/contract/tests/selective/a/A.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests.selective.a;
+
+import com.google.java.contract.Invariant;
+
+ at Invariant("false")
+public class A {
+}
diff --git a/test/com/google/java/contract/tests/selective/a/x/X.java b/test/com/google/java/contract/tests/selective/a/x/X.java
new file mode 100644
index 0000000..c166bbb
--- /dev/null
+++ b/test/com/google/java/contract/tests/selective/a/x/X.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests.selective.a.x;
+
+import com.google.java.contract.Invariant;
+
+ at Invariant("false")
+public class X {
+}
diff --git a/test/com/google/java/contract/tests/selective/a/x/X1.java b/test/com/google/java/contract/tests/selective/a/x/X1.java
new file mode 100644
index 0000000..27a7c41
--- /dev/null
+++ b/test/com/google/java/contract/tests/selective/a/x/X1.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests.selective.a.x;
+
+import com.google.java.contract.Invariant;
+
+ at Invariant("false")
+public class X1 {
+}
diff --git a/test/com/google/java/contract/tests/selective/b/B.java b/test/com/google/java/contract/tests/selective/b/B.java
new file mode 100644
index 0000000..d45a573
--- /dev/null
+++ b/test/com/google/java/contract/tests/selective/b/B.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests.selective.b;
+
+import com.google.java.contract.Invariant;
+
+ at Invariant("false")
+public class B {
+}
diff --git a/test/com/google/java/contract/tests/selective/b/y/Y.java b/test/com/google/java/contract/tests/selective/b/y/Y.java
new file mode 100644
index 0000000..469a52a
--- /dev/null
+++ b/test/com/google/java/contract/tests/selective/b/y/Y.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.google.java.contract.tests.selective.b.y;
+
+import com.google.java.contract.Invariant;
+
+ at Invariant("false")
+public class Y {
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/libcofoja-java.git
More information about the debian-med-commit
mailing list